I have a file that relies on an exported const variable. This variable is set to true but if ever needed can be set to false manually to prevent some behavior if downstream services request it.
I am not sure how to mock a const variable in Jest so that I can change it's value for testing the true and false conditions.
Example:
//constants module
export const ENABLED = true;
//allowThrough module
import { ENABLED } from './constants';
export function allowThrough(data) {
return (data && ENABLED === true)
}
// jest test
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
describe('allowThrough', () => {
test('success', () => {
expect(ENABLED).toBE(true);
expect(allowThrough({value: 1})).toBe(true);
});
test('fail, ENABLED === false', () => {
//how do I override the value of ENABLED here?
expect(ENABLED).toBe(false) // won't work because enabled is a const
expect(allowThrough({value: 1})).toBe(true); //fails because ENABLED is still true
});
});
This example will work if you compile ES6 modules syntax into ES5, because in the end, all module exports belong to the same object, which can be modified.
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
import * as constants from './constants';
describe('allowThrough', () => {
test('success', () => {
constants.ENABLED = true;
expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});
test('fail, ENABLED === false', () => {
constants.ENABLED = false;
expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});
Alternatively, you can switch to raw commonjs require function, and do it like this with the help of jest.mock(...):
const mockTrue = { ENABLED: true };
const mockFalse = { ENABLED: false };
describe('allowThrough', () => {
beforeEach(() => {
jest.resetModules();
});
test('success', () => {
jest.mock('./constants', () => mockTrue)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');
expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});
test('fail, ENABLED === false', () => {
jest.mock('./constants', () => mockFalse)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');
expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});
Unfortunately none of the posted solutions worked for me or to be more precise some did work but threw linting, TypeScript or compilation errors, so I will post my solution that both works for me and is compliant with current coding standards:
// constants.ts
// configuration file with defined constant(s)
export const someConstantValue = true;
// module.ts
// this module uses the defined constants
import { someConstantValue } from './constants';
export const someCheck = () => someConstantValue ? 'true' : 'false';
// module.test.ts
// this is the test file for module.ts
import { someCheck } from './module';
// Jest specifies that the variable must start with `mock`
const mockSomeConstantValueGetter = jest.fn();
jest.mock('./constants', () => ({
get someConstantValue() {
return mockSomeConstantValueGetter();
},
}));
describe('someCheck', () => {
it('returns "true" if someConstantValue is true', () => {
mockSomeConstantValueGetter.mockReturnValue(true);
expect(someCheck()).toEqual('true');
});
it('returns "false" if someConstantValue is false', () => {
mockSomeConstantValueGetter.mockReturnValue(false);
expect(someCheck()).toEqual('false');
});
});
There is another way to do it in ES6+ and jest 22.1.0+ thanks to getters and spyOn.
By default, you cannot spy on primitive types like boolean or number. You can though replace an imported file with your own mock. A getter method still acts like a primitive member but allows us to spy on it. Having a spy on our target member you can basically do with it whatever you want, just like with a jest.fn() mock.
Below an example
// foo.js
export const foo = true; // could be expression as well
// subject.js
import { foo } from './foo'
export default () => foo
// subject.spec.js
import subject from './subject'
jest.mock('./foo', () => ({
get foo () {
return true // set some default value
}
}))
describe('subject', () => {
const mySpy = jest.spyOn(subject.default, 'foo', 'get')
it('foo returns true', () => {
expect(subject.foo).toBe(true)
})
it('foo returns false', () => {
mySpy.mockReturnValueOnce(false)
expect(subject.foo).toBe(false)
})
})
Read more in the docs.
Thanks to #Luke I was able to expand on his answer for my needs. I had the requirements of:
Only mocking certain values in the file - not all
Running the mock only inside a single test.
Turns out that doMock() is like mock() but doesn't get hoisted. In addition requireActual() can be used to grab original data.
My config.js file - I need to mock only part of it
export const SOMETHING = 'blah'
export const OTHER = 'meh'
My test file
// import { someFunc } from 'some/file' // This won't work with doMock - see below
describe('My test', () => {
test('someFunc() does stuff', async () => {
// Here I mock the config file which gets imported somewhere deep in my code
jest.doMock('config.js', () => {
// Grab original
const originalModule = jest.requireActual('config')
// Return original but override some values
return {
__esModule: true, // Depends on your setup
...originalModule,
SOMETHING: 'boom!'
}
})
// Because `doMock` doesn't get hoisted we need to import the function after
const { someFunc } = await import(
'some/file'
)
// Now someFunc will use the original config values but overridden with SOMETHING=boom!
const res = await someFunc()
})
})
Depending on other tests you may also need to use resetModules() somewhere such as beforeAll or afterAll.
Docs:
doMock
requireActual
resetModules
Since we can't override/mock the value directly. we can use the below hack
// foo.js
export const foo = true; // could be expression as well
// spec file
import * as constants from './foo'
Object.defineProperty(constant, 'foo', {value: 1})
For functions:
Object.defineProperty(store, 'doOneThing', {value: jest.fn()})
For me the simplest solution was to redefine the imported object property, as decribed here:
https://flutterq.com/how-to-mock-an-exported-const-in-jest/
// foo.js
export const foo = true; // could be expression as well
// spec file
import * as constants from './foo'
Object.defineProperty(constant, 'foo', {value: 1, writable: true})
Facing the same issue, I found this blog post very useful, and much simpler than #cyberwombat use case :
https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
The most common scenario I needed was to mock a constant used by a class (in my case, a React component but it could be any ES6 class really).
#Luke's answer worked great for this, it just took a minute to wrap my head around it so I thought I'd rephrase it into a more explicit example.
The key is that your constants need to be in a separate file that you import, so that this import itself can be stubbed/mocked by jest.
The following worked perfectly for me.
First, define your constants:
// src/my-component/constants.js
const MY_CONSTANT = 100;
export { MY_CONSTANT };
Next, we have the class that actually uses the constants:
// src/my-component/index.jsx
import { MY_CONSTANT } from './constants';
// This could be any class (e.g. a React component)
class MyComponent {
constructor() {
// Use the constant inside this class
this.secret = MY_CONSTANT;
console.log(`Current value is ${this.secret}`);
}
}
export default MyComponent
Lastly, we have the tests. There's 2 use cases we want to handle here:
Mock the generate value of MY_CONSTANT for all tests inside this file
Allow the ability for a specific test to further override the value of MY_CONSTANT for that single test
The first part is acheived by using jest.mock at the top of your test file.
The second is acheived by using jest.spyOn to further spy on the exported list of constants. It's almost like a mock on top of a mock.
// test/components/my-component/index.js
import MyComponent from 'src/my-component';
import allConstants from 'src/my-component/constants';
jest.mock('src/my-component/constants', () => ({
get MY_CONSTANT () {
return 30;
}
}));
it('mocks the value of MY_CONSTANT', () => {
// Initialize the component, or in the case of React, render the component
new MyComponent();
// The above should cause the `console.log` line to print out the
// new mocked value of 30
});
it('mocks the value of MY_CONSTANT for this test,', () => {
// Set up the spy. You can then use any jest mocking method
// (e.g. `mockReturnValue()`) on it
const mySpy = jest.spyOn(allConstants, 'MY_CONSTANT', 'get')
mySpy.mockReturnValue(15);
new MyComponent();
// The above should cause the `console.log` line to print out the
// new mocked value of 15
});
One of the way for mock variables is the follow solution:
For example exists file ./constants.js with constants:
export const CONSTATN_1 = 'value 1';
export const CONSTATN_2 = 'value 2';
There is also a file of tests ./file-with-tests.spec.js in which you need to do mock variables.
If you need to mock several variables you need to use jest.requireActual to use the real values of the remaining variables.
jest.mock('./constants', () => ({
...jest.requireActual('./constants'),
CONSTATN_1: 'mock value 1',
}));
If you need to mock all variables using jest.requireActual is optional.
jest.mock('./constants', () => ({
CONSTATN_1: 'mock value 1',
CONSTATN_2: 'mock value 2'
}));
Instead of Jest and having trouble with hoisting etc. you can also just redefine your property using "Object.defineProperty"
It can easily be redefined for each test case.
This is a pseudo code example based on some files I have:
From localization file:
export const locale = 'en-US';
In another file we are using the locale:
import { locale } from 'src/common/localization';
import { format } from 'someDateLibrary';
// 'MMM' will be formatted based on locale
const dateFormat = 'dd-MMM-yyyy';
export const formatDate = (date: Number) => format(date, dateFormat, locale)
How to mock in a test file
import * as Localization from 'src/common/localization';
import { formatDate } from 'src/utils/dateUtils';
describe('format date', () => {
test('should be in Danish format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'da-DK'
});
expect(formatDate(1589500800000)).toEqual('15-maj-2020');
});
test('should be in US format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'en-US'
});
expect(formatDate(1589500800000)).toEqual('15-May-2020');
});
});
in typescript, you can not overwrite constant value but; you can overwrite the getter function for it.
const mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter = jest.fn();
jest.mock('lib/constants', () => ({
...jest.requireActual('lib/constants'),
get NEXT_PUBLIC_ENABLE_HCAPTCHA() {
return mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter();
},
}));
and in the test use as
beforeEach(() => {
mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter.mockReturnValue('true');
});
Thank you all for the answers.
In my case this was a lot simpler than all the suggestions here
// foo.ts
export const foo = { bar: "baz" };
// use-foo.ts
// this is just here for the example to have a function that consumes foo
import { foo } from "./foo";
export const getFoo = () => foo;
// foo.spec.ts
import "jest";
import { foo } from "./foo";
import { getFoo } from "./use-foo";
test("foo.bar should be 'other value'", () => {
const mockedFoo = foo as jest.Mocked<foo>;
mockedFoo.bar = "other value";
const { bar } = getFoo();
expect(bar).toBe("other value"); // success
expect(bar).toBe("baz"); // fail
};
Hope this helps someone.
../../../common/constant/file (constants file path)
export const Init = {
name: "",
basePath: "",
description: "",
thumbnail: "",
createdAt: "",
endDate: "",
earnings: 0,
isRecurring: false,
status: 0,
};
jest file
jest.mock('../../../common/constant/file',()=>({
get Init(){
return {isRecurring: true}
}
}))
it('showActionbutton testing',()=>{
const {result} = renderHook(() => useUnsubscribe())
expect(result.current.showActionButton).toBe(true)
})
index file
import {Init} from ../../../common/constant/file
const useUsubscribe(){
const showActionButton = Init.isRecurring
return showActionButton
}
I solved this by initializing constants from ContstantsFile.js in reducers. And placed it in redux store. As jest.mock was not able to mock the contstantsFile.js
constantsFile.js
-----------------
const MY_CONSTANTS = {
MY_CONSTANT1: "TEST",
MY_CONSTANT2: "BEST",
};
export defualt MY_CONSTANTS;
reducers/index.js
-----------------
import MY_CONST from "./constantsFile";
const initialState = {
...MY_CONST
}
export const AbcReducer = (state = initialState, action) => {.....}
ABC.jsx
------------
import { useSelector } from 'react-redux';
const ABC = () => {
const const1 = useSelector(state) => state. AbcReducer. MY_CONSTANT1:
const const2 = useSelector(state) => state. AbcReducer. MY_CONSTANT2:
.......
Now we can easily mock the store in test.jsx and provide the values to constant that we want.
Abc.text.jsx
-------------
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
describe('Abc mock constants in jest', () => {
const mockStore = configureMockStore([thunk]);
let store = mockStore({
AbcReducer: {
MY_CONSTANT1 ="MOCKTEST",
MY_CONSTANT2 = "MOCKBEST",
}
});
test('your test here', () => { .....
Now when the test runs it will always pick the constant value form mock store.
Related
I m new to Unit Test and currently writing Unit Test for my TypeScript app using Jest. I m using a custom package named common-packages where i store all of my constant, something simple like this
const CLIENT_ID = 1231123123;
const TEXT = {
Discover:"Discover",
loading: 'loading',
Type: "Testing",
}
const Constants = {
TEXT,
CLIENT_ID,
} as const;
export default Constants;
on my package, index.tsx. i export the Constant
export { default as Constants } from "./constants";
And the function that i want to test is this
import {Constants} from 'common-packages';
export const lockNextCardIfUncompleted = (data: any[]) => {
if (data.length !== 0) {
const locked = data.filter((e: any) => e.Type === Constants.TEXT.Type );
if (locked.length === 0) return false;
return true;
}
return false;
};
i have tried writing a unit test for lodash like this and it is working
const lodash = require('lodash')
test("repeat", () => {
expect(lodash.repeat("A",3)).toBe("AAA");
})
But for the unit test that have common-packages, it failed
import {
lockNextCardIfUncompleted,
} from './fileName';
test('should return locked', () => {
expect(
lockNextCardIfUncompleted([
{
Type: 'A',
},
]),
).toBe(false);
expect(
lockNextCardIfUncompleted([
{
Type: 'Testing,
},
]),
).toBe(true);
});
It return TypeError: Cannot read property 'TEXT' of undefined. So the test didnt recognize the Constant from common-package. can anybody please help me solve this issue?
thank you
You are using Default Exports, NOT Named Exports.
So you need to import the constants like this:
import Constants from 'common-packages';
This post follows up with my previous question:
previous question
I have come across a test which requires me to run mount in react native. I have gone through the documentation in jest and have found that before running the test suite you specifically need to setup a test environment capable of running jsdom for mount to work:
The link to docs is:
testEnvironment
Because of it's horrible documentation. I can't figure out how to create the customEnvironment class and what after that? what do I do with the global object? How to use it in my test file which currently looks like:
describe('Estimate', () => {
test('Estimate component Exists', () => {
const onPressFunction = jest.fn()
const obj = shallow(
<Estimate onPress={onPressFunction} />
)
expect(obj.find('TextInput').exists()).toBe(true)
})
test('Estimate returns value on button press', () => {
const onPressFunction = jest.fn()
const obj = shallow(
<Estimate onPress={onPressFunction} />
)
obj.find('TextInput').first().simulate('keypress', { key: '1' })
obj.find('Button').first().props().onPress()
expect(onPressFunction.toHaveBeenCalledWith('1'))
})
})
I just made it work had to import three packages from npm:
jsdom
react-native-mock-renderer
jest-environment-jsdom
Also my setup.mjs file looks like:
// #note can't import shallow or ShallowWrapper specifically
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
// eslint-disable-next-line
import { format } from 'prettier'
Enzyme.configure({ adapter: new Adapter() })
// Make Enzyme functions available in all test files without importing
global.shallow = Enzyme.shallow
Enzyme.ShallowWrapper.prototype.jsx = function jsx () {
const placeholder = '{ something: null }'
const obj = this.debug({ ignoreProps: false, verbose: true }).replace(/{\.\.\.}/g, placeholder)
return format(obj, {
parser: 'babylon',
filepath: 'test/setup.mjs',
trailingComma: 'all',
semi: false,
arrowParens: 'always',
})
.replace(new RegExp(placeholder, 'g'), '{...}')
.replace(';<', '<')
}
// the html function just throws errors so it's just reset to be the jsx function
Enzyme.ShallowWrapper.prototype.html = Enzyme.ShallowWrapper.prototype.jsx
jest.mock('react-native-device-info', () => {
return {
getDeviceLocale: () => 'en',
getDeviceCountry: () => 'US',
}
})
jest.mock('react-native-custom-tabs', () => ({
CustomTabs: {
openURL: jest.fn(),
},
}))
jest.mock('react-native-safari-view', () => ({
isAvailable: jest.fn(),
show: jest.fn(),
}))
const { JSDOM } = require('jsdom')
const jsdom = new JSDOM()
const { window } = jsdom
function copyProps (src, target) {
const props = Object.getOwnPropertyNames(src)
.filter((prop) => typeof target[prop] === 'undefined')
.map((prop) => Object.getOwnPropertyDescriptor(src, prop))
Object.defineProperties(target, props)
}
global.window = window
global.document = window.document
global.navigator = {
userAgent: 'node.js',
}
copyProps(window, global)
Enzyme.configure({ adapter: new Adapter() })
// Ignore React Web errors when using React Native
// allow other errors to propagate if they're relevant
const suppressedErrors = /(React does not recognize the.*prop on a DOM element|Unknown event handler property|is using uppercase HTML|Received `true` for a non-boolean attribute `accessible`|The tag.*is unrecognized in this browser)/
const realConsoleError = console.error
console.error = (message) => {
if (message.match(suppressedErrors)) {
return
}
realConsoleError(message)
}
require('react-native-mock-render/mock')
Test looks like:
test('Estimate returns value on button press', () => {
const onPressFunction = jest.fn()
const tree = mount(
<Estimate onPress={onPressFunction} />
)
console.log(tree.children().first().html())
})
Works like a charm!
I have a subscriber that dispatches an action based on the parameters supplied to a pub event
// subscriptions.js
import store from '../store';
import { action } from '../actions';
export const subscribeToToggle = () => {
window.$.subscribe('action/toggle', (_e, isToggleOn) => {
if (isToggleOn=== true){
store.dispatch(action());
}
});
};
In my test file I write 2 tests that test that the action is sent only when true is supplied.
// subscriptions.test.js
import { subscribeToToggle } from './subscriptions';
import jQuery from 'jquery';
import { actionTypes } from '../constants';
import store from '../store';
jest.mock('../store');
beforeEach(() => {
window.$ = jQuery;
window.$.unsubscribe('action/toggle');
});
test('Action not sent when false', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', false);
expect(store.getActions().length).toBe(0);
});
test('Action sent when true', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', true);
expect(store.getActions().length).toBe(1);
expect(store.getActions()[0].type).toBe(actionTypes.ACTION);
});
I have the following mocked store using redux-test-utils
import { createMockStore } from 'redux-test-utils';
let store = null;
store = createMockStore('');
export default store;
The issue I face is that my test only pass when the false test comes first. If they are the other way around the 'Action not sent when false' test fails as it sees the action supplied by the 'Action sent when true' test.
Is there any way for me to use the beforeEach method to reset the mocked store object?
In this case, the problem is that your store is essentially a singleton. This can create issues when you are trying to do things like this and is generally kind of an anti-pattern.
Instead of exporting a store object, it'd probably be better if you exported a getStore() function which could be called to get the store. In that case, you could then do:
getStore().dispatch(action());
Inside of that, you could then have other helper functions to be able to replace the store that is being returned by it. That file could look something like this:
import { createMockStore } from 'redux-test-utils';
let store = createMockStore('');
export default () => store;
Then, inside of there, you can add another which could be resetStore as a non-default export:
export const resetStore = () => store = createMockStore('');
It would still technically be a singleton, but it's now a singleton you can have some control over.
Then, in your beforeEach() in your tests, just call resetStore():
import { resetStore } from '../store';
beforeEach(() => {
resetStore();
});
This would also require you to update your real code to use getStore() instead of store directly, but it'll probably be a beneficial change in the long run.
Complete Updated Version:
// subscriptions.js
import getStore from '../store';
import { action } from '../actions';
export const subscribeToToggle = () => {
window.$.subscribe('action/toggle', (_e, isToggleOn) => {
if (isToggleOn=== true){
getStore().dispatch(action());
}
});
};
// subscriptions.test.js
import { subscribeToToggle } from './subscriptions';
import jQuery from 'jquery';
import { actionTypes } from '../constants';
import getStore, { resetStore } from '../store';
jest.mock('../store');
beforeEach(() => {
window.$ = jQuery;
window.$.unsubscribe('action/toggle');
resetStore();
});
test('Action not sent when false', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', false);
expect(getStore().getActions().length).toBe(0);
});
test('Action sent when true', () => {
subscribeToToggleOpen();
window.$.publish('action/toggle', true);
expect(getStore().getActions().length).toBe(1);
expect(getStore().getActions()[0].type).toBe(actionTypes.ACTION);
});
import { createMockStore } from 'redux-test-utils';
let store;
export const resetStore = () => { store = createMockStore(''); }
resetStore(); // init the store, call function to keep DRY
export default () => store;
Beyond that, the other way would be to have a global reducer which could reset the state of the store to it's default, but that would be messier and I don't really think would generally fit with writing a unit test.
I have the following component script (some irrelevant bits removed):
import api from '#/lib/api';
export default {
methods: {
upload (formData) {
api.uploadFile(formData).then(response => {
this.$emit('input', response.data);
});
}
}
};
And I have the following test, which uses avoriaz to mount the Vue instance:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadForm from '#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.api = api;
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
My sinon spy is never called, and I've tried a couple different variations on the above. What is the best way to spy on an imported function like this? Or am I conceptually approaching this the wrong way?
Problem
You need to stub the api dependency, which is a dependency of the file. This can't be done through the vue instance, since api is not a part of the vue component.
You need to stub the file dependency.
Solution
One method to do this is to use inject-loader.
Steps
Install inject-loader
npm install --save-dev inject-loader
At the top of your file, import UploadForm with inject-loader and vue-loader:
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
This is a factory function that returns UploadForm with dependencies stubbed.
Now, in your test you need to call UploadFormFactory with the dependency you want stubbed:
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
So your test file will look like:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
More info
I've written a tutorial with more detail here - https://www.coding123.org/stub-dependencies-vue-unit-tests/
I think Edd's answer is the most encompassing for most scenarios, so I'm marking his as the accepted answer. However, the workaround I came up with was to make the api library a global service (Vue.prototype.$api = api) in my main.js file, and then overwrite the global with a stub before each test.
describe('UploadForm.vue', () => {
let wrapper;
const uploadFile = sinon.stub().returns(Promise.resolve({ data: 0 }));
beforeEach(() => {
wrapper = mount(UploadForm, {
globals: {
$api: { uploadFile }
}
});
});
// ...
I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --
The application code:
// myModule.js
import dependency from './dependency';
export default (x) => {
dependency.doSomething(x * 2);
}
And the test code:
//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
spyOn(dependency, 'doSomething');
myModule(2);
expect(dependency.doSomething).toHaveBeenCalledWith(4);
});
});
What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.
The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.
// myModule.js
export default (x) => {
const dependency = require('./dependency'); // Yuck
dependency.doSomething(x * 2);
}
//myModule-test.js
describe('myModule', () => {
it('calls the dependency with double the input', () => {
jest.mock('../dependency');
myModule(2);
const dependency = require('../dependency'); // Also yuck
expect(dependency.doSomething).toBeCalledWith(4);
});
});
For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.
Edit: Several years have passed and this isn't really the right way to do this any more (and probably never was, my bad).
Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.
I'm leaving this answer in its original form for historical purposes, but you should really use jest.spyOn or jest.mock. Refer to the jest docs or the other answers on this page for details.
Original answer follows:
I've been able to solve this by using a hack involving import *. It even works for both named and default exports!
For a named export:
// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';
export default (x) => {
doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.doSomething = jest.fn(); // Mutate the named export
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Or for a default export:
// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies
export default (x) => {
dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.default = jest.fn(); // Mutate the default export
myModule(2);
expect(dependency.default).toBeCalledWith(4); // Assert against the default
});
});
You have to mock the module and set the spy by yourself:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}))
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export
Using only ES6 module syntax:
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.
To mock an ES6 dependency module default export using Jest:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);
describe('myModule', () => {
it('calls the dependency once with double the input', () => {
myModule(2);
expect(dependency).toHaveBeenCalledTimes(1);
expect(dependency).toHaveBeenCalledWith(4);
});
});
The other options didn't work for my case.
Adding more to Andreas' answer. I had the same problem with ES6 code, but I did not want to mutate the imports. That looked hacky. So I did this:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
And added file dependency.js in the " __ mocks __" folder parallel to file dependency.js. This worked for me. Also, this gave me the option to return suitable data from the mock implementation. Make sure you give the correct path to the module you want to mock.
The question is already answered, but you can resolve it like this:
File dependency.js
const doSomething = (x) => x
export default doSomething;
File myModule.js
import doSomething from "./dependency";
export default (x) => doSomething(x * 2);
File myModule.spec.js
jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";
describe('myModule', () => {
it('calls the dependency with double the input', () => {
doSomething.mockImplementation((x) => x * 10)
myModule(2);
expect(doSomething).toHaveBeenCalledWith(4);
console.log(myModule(2)) // 40
});
});
None of the answers here seemed to work for me (the original function was always being imported rather than the mock), and it seems that ESM support in Jest is still work in progress.
After discovering this comment, I found out that jest.mock() does not actually work with regular imports, because the imports are always run before the mock (this is now also officially documented). Because of this, I am importing my dependencies using await import(). This even works with a top-level await, so I just have to adapt my imports:
import { describe, expect, it, jest } from '#jest/globals';
jest.unstable_mockModule('../dependency', () => ({
doSomething: jest.fn()
}));
const myModule = await import('../myModule');
const dependency = await import('../dependency');
describe('myModule', async () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
I solved this another way. Let's say you have your dependency.js
export const myFunction = () => { }
I create a depdency.mock.js file besides it with the following content:
export const mockFunction = jest.fn();
jest.mock('dependency.js', () => ({ myFunction: mockFunction }));
And in the test, before I import the file that has the dependency, I use:
import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'
it('my test', () => {
mockFunction.returnValue(false);
functionThatCallsDep();
expect(mockFunction).toHaveBeenCalled();
})
I tried all the solutions and none worked or were showing lots of TS errors.
This is how I solved it:
format.ts file:
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
class Format {
parseXml (xml: string) {
return camelcaseKeys(parse(xml), {
deep: true,
})
}
}
const format = new Format()
export { format }
format.test.ts file:
import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))
describe('parseXml', () => {
test('functions called', () => {
const result = format.parseXml('XML')
expect(parse).toHaveBeenCalledWith('XML')
expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
expect(result).toBe('camel cased')
})
})
I made some modifications on #cam-jackson original answer and side effects has gone. I used lodash library to deep clone the object under test and then made any modification I want on that object. But be ware that cloning heavy objects can have negative impact on test performance and test speed.
objectUndertest.js
const objectUnderTest = {};
export default objectUnderTest;
objectUnderTest.myFunctionUnterTest = () => {
return "this is original function";
};
objectUndertest.test.js
import _ from "lodash";
import objectUndertest from "./objectUndertest.js";
describe("objectUndertest", () => {
let mockObject = objectUndertest;
beforeEach(() => {
mockObject = _.cloneDeep(objectUndertest);
});
test("test function", () => {
mockObject.myFunctionUnterTest = () => {
return "this is mocked function.";
};
expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
});
});