Mocking a module imported in a vue component with Jest - javascript

I'm having some issues processing the documentation of Jest, because I expected this code to work:
import Vue from 'vue';
import Router from '#/router/index';
import OrdersService from '#/services/orders.service';
jest.mock('#/services/orders.service');
describe('OrdersItem.vue', () => {
beforeEach(() => {
// mockClear does not exist
OrdersService.mockClear();
});
it('should render expected list contents', () => {
// Orders.mock is undefined
OrdersService.getAll.mockResolvedValue([ ... ]);
// ...
However it does not. It fails as-if OrdersService was never mocked. I've also tried stuff like:
jest.mock('#/services/orders.service', () => jest.fn());
jest.mock('#/services/orders.service', () => { getAll: jest.fn() });
First one replaces the whole service with a mock function (I'd like to achieve that automatic mocking functionality mentioned in the docs, where all the methods from the original are auto-replaced with mock fn).
Second one fails same way as the .mock call with just the module path.
What am I doing wrong here and why?
orders.service skeleton:
import axios from 'axios';
import config from '../config/config.json';
import Order from '../models/order';
class OrdersService {
constructor(httpClient) {
this.httpClient = httpClient;
}
getAll() {
// ...
}
}
export default new OrdersService(axios);

It looks like there is an issue in with jest.mock (#4262) concerning moduleNameMapper for module resolvers, aliases, path, whatever you want to call using #/something.
// you cannot use a module resolver (i.e. '#')
jest.mock('#/services/orders.service');
// you must use the full path to the file for the import and mock
import OrdersService from '../../src/services/orders.service';
jest.mock('../../src/services/orders.service');
Stay tuned to updates on the issue, looks like the last update was on 9/28.
Secondly, provided you fix the issue above, you are exporting a class instance not the class itself, as is done in the Jest example. Therefore, you will not have access to the clearMock method on the OrdersService but rather you can call clearMock on each mocked method on the class instance.
// mockClear will be undefined
OrdersService.mockClear();
// mockClear is defined
OrdersService.getAll.mockClear();
If you want to export the instance as you are, you could just clear all mocks using jest.clearAllMocks in the beforeEach or loop through all methods and call mockClear on each. Otherwise exporting the class itself will give you access to OrdersService.mockClear which will ...
Clear all instances and calls to constructor and all methods (ref)
This seems to be useful in cases where the mocked class is being used/instantiated in another class that you are trying to test, as in the jest example.
All of this has been tested and confirmed using Jest v23.6 and vue-cli v3.0.4.

Since the OrdersService is an instance of the class, it will return an object and you would need to mock all the properties exposed by this object manually.
You could try with the following implementation to mock your function. Reference docs
OrdersService.getAll = jest.fn(()=>{
// mock implementation here;
});
Hope this helps :)

You could try calling jest.resetModules() in the beforeEach block, that might cause the mocked service to be used

Try to import everything with an alias and set the mock on the alias.
import * as OrdersModule from '#/services/orders.service';
OrdersModule.getAll = jest.fn()
I found it in the bible:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Related

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

How can I can a mock an external class using jest?

I currently have the following Vue page code:
<template>
// Button that when clicked called submit method
</template>
<script>
import { moveTo } from '#/lib/utils';
export default {
components: {
},
data() {
},
methods: {
async submit() {
moveTo(this, COMPLETE_ACTION.path, null);
},
},
};
</script>
and then I have a test file for this page. My issue is that Im trying to check and assert that the moveTo method is called with the correct parameters using Jest. It keeps showing expected undefined but received an object. Here are the key points from the test file:
import * as dependency from '#/lib/utils';
dependency.moveTo = jest.fn();
// I then trigger the button call which calls the submit method on the page
expect(dependency.moveTo).toHaveBeenCalledWith(this, COMPLETE_ACTION.path, null);
Im unsure what this is in this context and what I should actually be passing in. Just to note I am using the mount helper from vue test utils.
I solved my issue and it was the this param within the test. This was undefined in the test and was expecting to match against a VueComponent.
I used my wrapper and then accessed the VueComponent by referencing the vm property as per the documentation: https://vue-test-utils.vuejs.org/api/wrapper/#properties
In turn I updated the following line and added wrapper.vm
expect(dependency.moveTo).toHaveBeenCalledWith(wrapper.vm, COMPLETE_ACTION.path, null);
You need to mock the module itself. In your case you are making an assertion on a spy function that is never called.
You can add a module mock by creating a "mocks/ subdirectory immediately adjacent to the module". For a node module "If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the mocks directory adjacent to node_modules".
In your case (there are other approaches) you need to create a __mocks__ folder adjacent to the node_modules one and create a file at __mocks__/lib/utils/index.js and export the mocked function:
export const moveTo = jest.fn()

Jest: How to mock default export Component when same module also has named export?

I have an ES6 module that exports a React Component class by default, but also exports a plain JS function as a named export. When testing other packages that use this module, I want to mock both the default exported component and named exported function to keep my unit tests pure.
The module looks something like this:
import React, { Component } from 'react';
export default class MyComponent extends Component {
render() {
return <div>Hello</div>
}
}
export function myUtilityFunction() { return 'foo' };
I would like to use the following syntax to mock the exports:
import React from 'react';
import MyComponent, { myUtilityFunction } from './module';
jest.mock('./module');
MyComponent.mockImplementation(() => 'MockComponent');
myUtilityFunction.mockImplementation(() => 'foo');
When I try to use this syntax, however, MyComponent does not appear to be mocked when used within other components. When I try to mock MyComponent like this and render it on its own, it renders out to null.
This behavior is very strange, because if I use the exact same syntax, but both imports are JavaScript functions, the mocking works as expected. See the StackOverflow issue I opened here that confirms that the syntax works when the imports are both functions.
Here is a GitHub repo demoing the problem, as well as several solutions I've tried: https://github.com/zpalexander/jest-enzyme-problem
You can build the repo and run the tests with yarn install && yarn test
Thanks!
The other solution didn't work for me. This is how I did:
jest.mock('./module', () => ({
__esModule: true,
myUtilityFunction: 'myUtilityFunction',
default: 'MyComponent'
}));
Another way to do it:
jest.unmock('../src/dependency');
const myModule = require('../src/dependency');
myModule.utilityFunction = 'your mock'
I think the issue is that the ShallowWrapper class's getElement method needs to be passed a class that contains a render method. To do that, your MyComponent.mockImplementation needs to more fully mock a class constructor.
For details on how to mock a class constructor, see the Jest documentation starting at "mockImplementation can also be used to mock class constructors:" https://facebook.github.io/jest/docs/en/mock-function-api.html#mockfnmockimplementationfn
Using the Jest documentation as a model, we can mock the MyComponent class constructor and make it shallow renderable by enzyme like this:
MyComponent.mockImplementation(() => {
return {
render: () => <div>MockComponent</div>
};
});
Now when getElement goes looking for a render method it will find it.
Here's a gist that implements this change on the App.mockImplementation.test.js file from your repo: https://gist.github.com/timothyjellison/a9c9c2fdfb0b30aab5698dd92e901b24

Jest mocking default exports - require vs import

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

Mocking/stubbing objects that are only defined in a closure

First of all, for testing my library, I'm using Mocha and Chai, but I'm probably going to need Sinon too sometime.
This is the library:
import Service from 'service'; // a third-party module out of my control
const service = Service(...);
class MyLib {
... uses `service` in a bunch of different ways ...
... service.put(foo) ...
... service.get(bar) ...
}
export default MyLib;
This is basically the test file:
import MyLib from '../my-lib.js';
describe('MyLib', () => {
describe('a method that uses the service', () => {
...
The service object makes some calls to remote servers, which I can't really do in the tests. Therefore, I'm thinking I should stub the service's methods or mock the entire service object. However, since the object is constant and only reachable through the MyLib closure, I don't know how.
Ideally I don't wish to change the API of MyLib to e.g. inject the service object in the constructor.
I use Babel 6 with the es2015 preset, if it matters.
How should I approach this?
There are a few ways to do it.
The simplest way without extra libraries
Save service as a class property and call it from there:
import Service from 'service';
const service = Service(...);
class MyLib {
constructor() {
this.service = service;
}
... now you should call service in a bit different way
... this.service.put(foo) ...
... this.service.get(bar) ...
}
export default MyLib;
Then you can rewrite service instance in your tests:
it('should call my mock', () => {
const lib = new MyLib();
lib.service = mockedService; // you need to setup this mock, with Sinon, for example
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Mock require() function
There are some libraries that allow you to override results of require() function. My favourite one is proxyquire. You can use it and your module will get mockedSerice instead of real:
import proxyquire from 'proxyquire';
it('should call my mock', () => {
const MyLib = proxyquire('./my-lib', {
// pass here the map of mocked imports
service: mockedService
})
const lib = new MyLib();
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Use rewire to get access into module closure
Rewire is a special library that instruments module code so then you can change any local variable there
import rewire from 'rewire';
it('should call my mock', () => {
const MyLib = rewire('./my-lib')
const lib = new MyLib();
// __set__ is a special secret method added by rewire
MyLib.__set__('service', mockedService);
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Also, there is a babel-plugin-rewire for better integration with your tools.
All methods above are nice you may pick that seems better for your issue.
I was dealing with the same thing recently.
You can take advantage of https://nodejs.org/api/modules.html#modules_caching
In the test you are writing, require/import the service the same way you do in the file you are testing.
Then use Sinon to stub the methods you are using within the file to test.
sinon.stub(Service, put).returns()
When the file requires the service, it will use the modified module.
I haven't tested your exact case, where you are creating instance of the service and only after that you work with it, but a bit of playing with it should help you achieve what you want, and it is all without any external libraries, only simple sinon stub.
Your tests for MyLib shouldn't test service, so I would suggest mocking all or most of it. You should just check that MyLib is calling the right functions on service with the right arguments.
I'm not sure about sinon, but that looks like a pretty standard way to import and use a library, I would be surprised if it doesn't support mocking service.

Categories

Resources