I searched for a very long time how to mock any module with jest (like rewire does). I finally manage to do it this way, and it work like a charm :
jest.mock('common/js/browser-utils', () => ({
openBrowser: jest.fn()
}));
const { openBrowser: openBrowserSpy } = jest.requireMock(
'common/js/browser-utils'
);
But i wonder if there is another fast way to do so ?
I saw the genMockFromModule method but i never makes it work (maybe it's not for that usage.)
What i want is simple : mocking a module by a jest.fn() (or any auto mechanism), then being able to access this jest.fn() in my tests (here: openBrowserSpy) to expect(assertions) on it
You can just auto-mock the module using jest.mock:
jest.mock('common/js/browser-utils');
The docs could probably be improved with a better description of what "auto-mocked version" means, but what happens is that Jest keeps the API surface of the module the same while replacing the implementation with empty mock functions.
Complete example
browser-utils.js
export const openBrowser = () => { /* do something */ };
code.js
import { openBrowser } from './browser-utils';
export const func = () => {
/* do stuff */
openBrowser();
/* do other stuff */
}
code.test.js
jest.mock('./browser-utils'); // create an auto-mock of the module
import { openBrowser } from './browser-utils'; // openBrowser is already an empty mock function
import { func } from './code';
test('func', () => {
func();
expect(openBrowser).toHaveBeenCalled(); // Success!
});
Bonus: Mock single function
To mock a single function you can use jest.spyOn like this:
import * as browserUtils from './browser-utils';
import { func } from './code';
test('func', () => {
const spy = jest.spyOn(browserUtils, 'openBrowser');
spy.mockImplementation(); // replace implementation with empty mock function (optional)
func();
expect(spy).toHaveBeenCalled(); // Success!
});
I've tried to search for answer to this problem for some time now and I failed. So I decided to give it a try here. If there is a such question already and I missed it, I'm sorry for duplicate.
Assume I have this javascript module 'myValidator.js', where are two functions and one function calls the other one.
export const validate = (value) => {
if (!value.something) {
return false
}
// other tests like that
return true
}
export const processValue = (value) => {
if (!validate(value)) {
return null
}
// do some stuff with value and return something
}
Test for this like this.
I want to test the validate function, whether is behaves correctly. And then I have the processValue function that invokes the first one and returns some value when validation is ok or null.
import * as myValidator from './myValidator'
describe('myValidator', () => {
describe('validate', () => {
it('should return false when something not defined', () => {
...
}
}
describe('processValue', () => {
it('should return something when value is valid', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => true)
expect(validate('something')).toEqual('somethingProcessed')
}
it('should return null when validation fails', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => false)
expect(validate('somethingElse')).toEqual(null)
}
}
}
Now, the problem is that this doesn't actually work as processValue() actually calls the function inside the module, because of the closure I suppose. So the function is not mocked as only the reference in exports is changed to jest mock, I guess.
I have found a solution for this and inside the module to use
if (!exports.validate(value))
That works for the tests. However we use Webpack (v4) to build the app, so it transforms those exports into its own structure and then when the application is started, the exports is not defined and the code fails.
What's best solution to test this?
Sure I can do it with providing some valid and invalid value, for this simple case that would work, however I believe it should be tested separately.
Or is it better to not mock functions and call it through to avoid the problem I have or is there some way how to achieve this with JavaScript modules?
I finally found the answer to this question. It's actually in the the Jest examples project on GitHub.
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* This file illustrates how to do a partial mock where a subset
* of a module's exports have been mocked and the rest
* keep their actual implementation.
*/
import defaultExport, {apple, strawberry} from '../fruit';
jest.mock('../fruit', () => {
const originalModule = jest.requireActual('../fruit');
const mockedModule = jest.genMockFromModule('../fruit');
//Mock the default export and named export 'apple'.
return {
...mockedModule,
...originalModule,
apple: 'mocked apple',
default: jest.fn(() => 'mocked fruit'),
};
});
it('does a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked fruit');
expect(defaultExport).toHaveBeenCalled();
expect(apple).toBe('mocked apple');
expect(strawberry()).toBe('strawberry');
});
I'm testing in a ES6 babel-node environment. I want to mock a method thats used inside of the method I'm importing. The challenging part seems to be that the method I want to mock is imported into the file where the method I want to test resides. I've explored proxyquire, babel-plugin-rewire but I can't seem to get them to work on methods imported inside of other imports. From reading through various github issues I get the feeling that this might be a known limitation/frustration. Is this not possible or am I missing something?
No errors are produced when using proxyquire or babel-plugin-rewire. The method just doesn't get mocked out and it returns the methods normal value.
Here's a generic example of the import situation.
// serviceLayer.js
getSomething(){
return 'something';
}
// actionCreator.js
import { getSomething } from './serviceLayer.js';
requestSomething(){
return getSomething(); <------- This is what I want to mock
}
// actionCreator.test.js
import test from 'tape';
import {requestSomething} from 'actionCreator.js'
test('should return the mock' , (t) => {
t.equal(requestSomething(), 'something else');
});
I'm answering my own question here... Turns out I was just using babel-plugin-rewire incorrectly. Here's an example of how I'm using it now with successful results.
// serviceLayer.js
export const getSomething = () => {
return 'something';
}
// actionCreator.js
import { getSomething } from './serviceLayer.js';
export const requestSomething = () => {
return getSomething(); <------- This is what I want to mock
}
// actionCreator.test.js
import test from 'tape';
import { requestSomething, __RewireApi__ } from 'actionCreator.js'
__RewireApi__.Rewire('getSomething' , () => {
return 'something else''
});
test('should return the mock' , (t) => {
t.equal(requestSomething(), 'something else');
});
I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).
I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.
My search.js file
export {
search,
}
function search(){
// does things
return Promise.resolve('foo')
}
My react component:
import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'
export default SearchContainer {
constructor(){
this.state = {
query: "hello world"
}
}
componentDidMount(){
search(this.state.query)
.then(result => { this.setState({ result, })})
}
render() {
return <SearchResults
result={this.state.result}
/>
}
}
In my unit tests, I want to check that the method search was called with the correct arguments.
My tests look something like that:
import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';
import SearchResults from './SearchResults';
let mockPromise;
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise)};
});
import SearchContainer from './SearchContainer';
describe('<SearchContainer />', () => {
it('should call the search module', () => {
const result = { foo: 'bar' }
mockPromise = Promise.resolve(result);
const wrapper = shallow(<SearchContainer />);
wrapper.instance().componentDidMount();
mockPromise.then(() => {
const searchResults = wrapper.find(SearchResults).first();
should(searchResults.prop('result')).equal(result);
})
})
});
I already had a hard time to figure out how to make jest.mock work, because it requires variables to be prefixed by mock.
But if I want to test arguments to the method search, I need to make the mocked function available in my tests.
If I transform the mocking part, to use a variable:
const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
return { search: mockSearch};
});
I get this error:
TypeError: (0 , _search.search) is not a function
Whatever I try to have access to the jest.fn and test the arguments, I cannot make it work.
What am I doing wrong?
The problem
The reason you're getting that error has to do with how various operations are hoisted.
Even though in your original code you only import SearchContainer after assigning a value to mockSearch and calling jest's mock, the specs point out that: Before instantiating a module, all of the modules it requested must be available.
Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.
One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.
Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mock call. So, the order of relevant operations will be something like:
Set a mock factory for ./search.js
Import all dependencies, which will call the mock factory for a function to give the component
Assign a value to mockSearch
When step 2 happens, the search function passed to the component will be undefined, and the assignment at step 3 is too late to change that.
Solution
If you create the mock function as part of the mock call (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.
As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.
Since you now know jest mocking actually happens before imports, a trivial approach would be:
import { search } from './search.js'; // This will actually be the mock
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise) };
});
[...]
beforeEach(() => {
search.mockClear();
});
it('should call the search module', () => {
[...]
expect(search.mock.calls.length).toBe(1);
expect(search.mock.calls[0]).toEqual(expectedArgs);
});
In fact, you might want to replace:
import { search } from './search.js';
With:
const { search } = require.requireMock('./search.js');
This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).
Additional note
All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as #publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:
import * as search from './search.js';
beforeEach(() => {
search.search = jest.fn(() => mockPromise);
});
In my case, I got this error because I failed to implement the mock correctly.
My failing code:
jest.mock('react-native-some-module', mockedModule);
When it should have been an arrow function...
jest.mock('react-native-some-module', () => mockedModule);
When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...
...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');
jest.mock('../../api/someApi.js', () => {
return {
GetSomeData: jest.fn()
};
});
beforeEach(() => {
// Clear any calls to the mocks.
SomeApi.GetSomeData.mockClear();
});
it("renders valid token", async () => {
const responseValue = {data:{
tokenIsValid:true}};
SomeApi.GetSomeData.mockResolvedValue(responseValue);
let wrapper = shallow(<MyPage {...props} />);
expect(wrapper).not.toBeNull();
await wrapper.update();
const state = wrapper.instance().state;
expect(state.tokenIsValid).toBe(true);
});
I have the following ES6 modules:
File network.js
export function getDataFromServer() {
return ...
}
File widget.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
I'm looking for a way to test Widget with a mock instance of getDataFromServer. If I used separate <script>s instead of ES6 modules, like in Karma, I could write my test like:
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
However, if I'm testing ES6 modules individually outside of a browser (like with Mocha + Babel), I would write something like:
import { Widget } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(?????) // How to mock?
.andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Okay, but now getDataFromServer is not available in window (well, there's no window at all), and I don't know a way to inject stuff directly into widget.js's own scope.
So where do I go from here?
Is there a way to access the scope of widget.js, or at least replace its imports with my own code?
If not, how can I make Widget testable?
Stuff I considered:
a. Manual dependency injection.
Remove all imports from widget.js and expect the caller to provide the deps.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
I'm very uncomfortable with messing up Widget's public interface like this and exposing implementation details. No go.
b. Expose the imports to allow mocking them.
Something like:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
then:
import { Widget, deps } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(deps.getDataFromServer) // !
.andReturn("mockData");
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
This is less invasive, but it requires me to write a lot of boilerplate for each module, and there's still a risk of me using getDataFromServer instead of deps.getDataFromServer all the time. I'm uneasy about it, but that's my best idea so far.
I've started employing the import * as obj style within my tests, which imports all exports from a module as properties of an object which can then be mocked. I find this to be a lot cleaner than using something like rewire or proxyquire or any similar technique. I've done this most often when needing to mock Redux actions, for example. Here's what I might use for your example above:
import * as network from 'network.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
If your function happens to be a default export, then import * as network from './network' would produce {default: getDataFromServer} and you can mock network.default.
Note: the ES spec defines modules as read-only, and many ES transpilers have started honoring this, which may break this style of spying. This is highly dependent on your transpiler as well as your test framework. For example, I think Jest performs some magic to make this work, though Jasmine does not, at least currently. YMMV.
carpeliam is correct, but note that if you want to spy on a function in a module and use another function in that module calling that function, you need to call that function as part of the exports namespace, otherwise the spy won't be used.
Wrong example:
// File mymodule.js
export function myfunc2() {return 2;}
export function myfunc1() {return myfunc2();}
// File tests.js
import * as mymodule
describe('tests', () => {
beforeEach(() => {
spyOn(mymodule, 'myfunc2').and.returnValue = 3;
});
it('calls myfunc2', () => {
let out = mymodule.myfunc1();
// 'out' will still be 2
});
});
Right example:
export function myfunc2() {return 2;}
export function myfunc1() {return exports.myfunc2();}
// File tests.js
import * as mymodule
describe('tests', () => {
beforeEach(() => {
spyOn(mymodule, 'myfunc2').and.returnValue = 3;
});
it('calls myfunc2', () => {
let out = mymodule.myfunc1();
// 'out' will be 3, which is what you expect
});
});
vdloo's answer got me headed in the right direction, but using both CommonJS "exports" and ES6 module "export" keywords together in the same file did not work for me (Webpack v2 or later complains).
Instead, I'm using a default (named variable) export wrapping all of the individual named module exports and then importing the default export in my tests file. I'm using the following export setup with Mocha/Sinon and stubbing works fine without needing rewire, etc.:
// MyModule.js
let MyModule;
export function myfunc2() { return 2; }
export function myfunc1() { return MyModule.myfunc2(); }
export default MyModule = {
myfunc1,
myfunc2
}
// tests.js
import MyModule from './MyModule'
describe('MyModule', () => {
const sandbox = sinon.sandbox.create();
beforeEach(() => {
sandbox.stub(MyModule, 'myfunc2').returns(4);
});
afterEach(() => {
sandbox.restore();
});
it('myfunc1 is a proxy for myfunc2', () => {
expect(MyModule.myfunc1()).to.eql(4);
});
});
I implemented a library that attempts to solve the issue of run time mocking of TypeScript class imports without needing the original class to know about any explicit dependency injection.
The library uses the import * as syntax and then replaces the original exported object with a stub class. It retains type safety so your tests will break at compile time if a method name has been updated without updating the corresponding test.
This library can be found here: ts-mock-imports.
I have found this syntax to be working:
My module:
// File mymod.js
import shortid from 'shortid';
const myfunc = () => shortid();
export default myfunc;
My module's test code:
// File mymod.test.js
import myfunc from './mymod';
import shortid from 'shortid';
jest.mock('shortid');
describe('mocks shortid', () => {
it('works', () => {
shortid.mockImplementation(() => 1);
expect(myfunc()).toEqual(1);
});
});
See the documentation.
I haven't tried it myself, but I think mockery might work. It allows you to substitute the real module with a mock that you have provided. Below is an example to give you an idea of how it works:
mockery.enable();
var networkMock = {
getDataFromServer: function () { /* your mock code */ }
};
mockery.registerMock('network.js', networkMock);
import { Widget } from 'widget.js';
// This widget will have imported the `networkMock` instead of the real 'network.js'
mockery.deregisterMock('network.js');
mockery.disable();
It seems like mockery isn't maintained anymore and I think it only works with Node.js, but nonetheless, it's a neat solution for mocking modules that are otherwise hard to mock.
I recently discovered babel-plugin-mockable-imports which handles this problem neatly, IMHO. If you are already using Babel, it's worth looking into.
See suppose I'd like to mock results returned from isDevMode() function in order to check to see how code would behave under certain circumstances.
The following example is tested against the following setup
"#angular/core": "~9.1.3",
"karma": "~5.1.0",
"karma-jasmine": "~3.3.1",
Here is an example of a simple test case scenario
import * as coreLobrary from '#angular/core';
import { urlBuilder } from '#app/util';
const isDevMode = jasmine.createSpy().and.returnValue(true);
Object.defineProperty(coreLibrary, 'isDevMode', {
value: isDevMode
});
describe('url builder', () => {
it('should build url for prod', () => {
isDevMode.and.returnValue(false);
expect(urlBuilder.build('/api/users').toBe('https://api.acme.enterprise.com/users');
});
it('should build url for dev', () => {
isDevMode.and.returnValue(true);
expect(urlBuilder.build('/api/users').toBe('localhost:3000/api/users');
});
});
Exemplified contents of src/app/util/url-builder.ts
import { isDevMode } from '#angular/core';
import { environment } from '#root/environments';
export function urlBuilder(urlPath: string): string {
const base = isDevMode() ? environment.API_PROD_URI ? environment.API_LOCAL_URI;
return new URL(urlPath, base).toJSON();
}
You can use putout-based library mock-import for this purpose.
Let's suppose you have a code you want to test, let it be cat.js:
import {readFile} from 'fs/promises';
export default function cat() {
const readme = await readFile('./README.md', 'utf8');
return readme;
};
And tap-based test with a name test.js will look this way:
import {test, stub} from 'supertape';
import {createImport} from 'mock-import';
const {mockImport, reImport, stopAll} = createMockImport(import.meta.url);
// check that stub called
test('cat: should call readFile', async (t) => {
const readFile = stub();
mockImport('fs/promises', {
readFile,
});
const cat = await reImport('./cat.js');
await cat();
stopAll();
t.calledWith(readFile, ['./README.md', 'utf8']);
t.end();
});
// mock result of a stub
test('cat: should return readFile result', async (t) => {
const readFile = stub().returns('hello');
mockImport('fs/promises', {
readFile,
});
const cat = await reImport('./cat.js');
const result = await cat();
stopAll();
t.equal(result, 'hello');
t.end();
});
To run test we should add --loader parameter:
node --loader mock-import test.js
Or use NODE_OPTIONS:
NODE_OPTIONS="--loader mock-import" node test.js
On the bottom level mock-import uses transformSource hook, which replaces on the fly all imports with constants declaration to such form:
const {readFile} = global.__mockImportCache.get('fs/promises');
So mockImport adds new entry into Map and stopAll clears all mocks, so tests not overlap.
All this stuff needed because ESM has it's own separate cache and userland code has no direct access to it.
Here is an example to mock an imported function
File network.js
export function exportedFunc(data) {
//..
}
File widget.js
import { exportedFunc } from 'network.js';
export class Widget() {
constructor() {
exportedFunc("data")
}
}
Test file
import { Widget } from 'widget.js';
import { exportedFunc } from 'network'
jest.mock('network', () => ({
exportedFunc: jest.fn(),
}))
describe("widget", function() {
it("should do stuff", function() {
let widget = new Widget();
expect(exportedFunc).toHaveBeenCalled();
});
});
I haven't been able to try it out yet, but (Live demo at codesandbox.io/s/adoring-orla-wqs3zl?file=/index.js)
If you have a browser-based test runner in theory you should be able to include a Service Worker that can intercept the request for the ES6 module you want to mock out and replace it with an alternative implementation (similar or the same as how Mock Service Worker approaches things)
So something like this in your service worker
self.addEventListener('fetch', (event) => {
if (event.request.url.includes("canvas-confetti")) {
event.respondWith(
new Response('const confetti=function() {}; export default confetti;', {
headers: { 'Content-Type': 'text/javascript' }
})
);
}
});
If your source code is pulling in an ES6 module like this
import confetti from 'https://cdn.skypack.dev/canvas-confetti';
confetti();