How to change the behaviour of a mocked import?
JavascriptReact NativeUnit TestingJestjsMockingJavascript Problem Overview
I am quite confused with mocking in Jest an how to unit test the implementations. The thing is i want to mock different expected behaviours.
Is there any way to achieve this? as imports can be only on the top of the file and to be able to mock something it must be declared before the import. I have also tried to pass a local function so I could overwrite the behaviour but jest complains you are not allowed to pass anything local.
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn(() => console.log('Hello'))
}));
import * as theThingToTest from '../../../app/actions/toTest'
import * as types from '../../../app/actions/types'
it('test1', () => {
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
it('test2', () => {
//the-package-to-mock.methodToMock should behave like something else
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
internally as you can imagine theThingToTest.someAction()
uses the-package-to-mock.methodToMock
Javascript Solutions
Solution 1 - Javascript
You can mock with a spy and import the mocked module. In your test you set how the mock should behave using mockImplementation
:
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn()
}));
import { methodToMock } from 'the-package-to-mock'
it('test1', () => {
methodToMock.mockImplementation(() => 'someValue')
})
it('test2', () => {
methodToMock.mockImplementation(() => 'anotherValue')
})
Solution 2 - Javascript
I use the following pattern:
'use strict'
const packageToMock = require('../path')
jest.mock('../path')
jest.mock('../../../../../../lib/dmp.db')
beforeEach(() => {
packageToMock.methodToMock.mockReset()
})
describe('test suite', () => {
test('test1', () => {
packageToMock.methodToMock.mockResolvedValue('some value')
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
test('test2', () => {
packageToMock.methodToMock.mockResolvedValue('another value')
expect(theThingToTest.someAction().type).toBe(types.OTHER_TYPE)
})
})
Explanation:
You mock the class you are trying to use on test suite level, make sure the mock is reset before each test and for every test you use mockResolveValue to describe what will be return when mock is returned
Solution 3 - Javascript
Another way is to use jest.doMock(moduleName, factory, options).
E.g.
the-package-to-mock.ts
:
export function methodToMock() {
return 'real type';
}
toTest.ts
:
import { methodToMock } from './the-package-to-mock';
export function someAction() {
return {
type: methodToMock(),
};
}
toTest.spec.ts
:
describe('45006254', () => {
beforeEach(() => {
jest.resetModules();
});
it('test1', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type A'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type A');
});
it('test2', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type B'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type B');
});
});
unit test result:
PASS examples/45006254/toTest.spec.ts
45006254
✓ test1 (2016 ms)
✓ test2 (1 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
toTest.ts | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.443 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/45006254
Solution 4 - Javascript
spyOn
worked best for us. See previous answer:
Solution 5 - Javascript
Andreas answer work well with functions, here is what I figured out using it:
// You don't need to put import line after the mock.
import {supportWebGL2} from '../utils/supportWebGL';
// functions inside will be auto-mocked
jest.mock('../utils/supportWebGL');
const mocked_supportWebGL2 = supportWebGL2 as jest.MockedFunction<typeof supportWebGL2>;
// Make sure it return to default between tests.
beforeEach(() => {
// set the default
supportWebGL2.mockImplementation(() => true);
});
it('display help message if no webGL2 support', () => {
// only for one test
supportWebGL2.mockImplementation(() => false);
// ...
});
It won't work if your mocked module is not a function. I haven't been able to change the mock of an exported boolean for only one test :/. My advice, refactor to a function, or make another test file.
export const supportWebGL2 = /* () => */ !!window.WebGL2RenderingContext;
// This would give you: TypeError: mockImplementation is not a function