Related
I'm simply trying to find out if a component method has been called after a store action, but I'm getting this error:
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function bound mockConstructor]
This is my unit test:
describe('MyComponent.spec.js', () => {
let methods = {
setLocation: jest.fn()
// more methods...
}
it('calls setLocation on undo/redo', () => {
let wrapper = mount(MyComponent, {
store,
localVue,
methods
})
store.dispatch('doUndo')
expect(wrapper.vm.setLocation).toHaveBeenCalled()
})
})
Not sure if this is a good practice or not, but I'm using the actual store and a local Vue instance.
To verify the mocked method, use the actual mock variable itself (not via wrapper):
expect(methods.setLocation).toHaveBeenCalled()
I'm trying to mock a class that creates and returns another class. I've got this:
const mockMethod = jest.fn();
const mockClassA = jest.fn<ClassA>(() => ({
method: mockMethod
}));
jest.mock("../src/ClassB", () => ({
ClassB: {
getClassA: () => new mockClassA()
}
}));
It gets caught out because of the hoisting, the mockClassA is undefined when jest is mocking `../src/ClassB".
I've read if you don't want hoisting, just use doMock instead:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://jestjs.io/docs/en/next/jest-object#jestdomockmodulename-factory-options
When I run with mock, I get TypeError: mockClassA is not a constructor, as mockClassA is undefined because the mock is hoisted above the definition for mockClassA.
When I change mock to doMock, it simply does not mock the module - it uses the real thing.
Edit: Declairing them in-line means I can't easily access the mocked methods for checking:
jest.mock("../src/ClassB", () => ({
ClassB: {
getClassA: () => ({
method: jest.fn()
})
}
}));
Because getClassA is a function, it's returning a separate instance of the object with method.
Edit 2: Ah! Managed to inline it like so:
jest.mock("../src/ClassB", () => {
const mockMethod: jest.fn();
return {
ClassB: {
getClassA: () => ({
method: mockMethod
})
}
};
});
I think you have 2 options here:
use jest.mock, inline the mockClassA and mockMethod, expose them in the mock and then import from '../src/ClassB'
use doMock, but use a dynamic require within your test case.
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 writing a Jest mock, but I seem to have a problem when defining a mocked function outside of the mock itself.
I have a class:
myClass.js
class MyClass {
constructor(name) {
this.name = name;
}
methodOne(val) {
return val + 1;
}
methodTwo() {
return 2;
}
}
export default MyClass;
And a file using it:
testSubject.js
import MyClass from './myClass';
const classInstance = new MyClass('Fido');
const testSubject = () => classInstance.methodOne(1) + classInstance.name;
export default testSubject;
And the test:
testSubject.test.js
import testSubject from './testSubject';
const mockFunction = jest.fn(() => 2)
jest.mock('./myClass', () => () => ({
name: 'Name',
methodOne: mockFunction,
methodTwo: jest.fn(),
}))
describe('MyClass tests', () => {
it('test one', () => {
const result = testSubject()
expect(result).toEqual('2Name')
})
})
However, I get the following error:
TypeError: classInstance.methodOne is not a function
If I instead write:
...
methodOne: jest.fn(() => 2)
Then the test passes no problem.
Is there a way of defining this outside of the mock itself?
In my case, I had to mock a Node.js module. I'm using React and Redux in ES6, with Jest and Enzyme for unit tests.
In the file I'm using, and writing a test for, I'm importing the node modules as default:
import nodeModulePackage from 'nodeModulePackage';
So I needed to mock it as a default since I kept getting the error (0, _blah.default) is not a function..
My solution was to do:
jest.mock('nodeModulePackage', () => jest.fn(() => {}));
In my case, I just needed to override the function and make it return an empty object.
If you need to call a function on that node module, you'll do the following:
jest.mock('nodeModulePackage', () => ({ doSomething: jest.fn(() => 'foo') }));
I figured this out. It is to do with hoisting, see: Jest mocking reference error
The reason it had worked in a previous test, where I had done it, was because the testSubject was itself a class. This meant that when the testSubject was instantiated, it was after the variable declaration in the test file, so the mock had access to use it.
So in the above case it was never going to work.
Defining mockOne as an unassigned let and then initialising the variable inside the mocking function worked for me:
let mockFunction
jest.mock('./myClass', () => () => {
mockFunction = jest.fn(() => 2)
return {
name: 'Name',
methodOne: mockFunction,
methodTwo: jest.fn(),
}
}))
In my case, it was the importing, as described at Getting `TypeError: jest.fn is not a function` (posting here because my problem showed in jest.mock, so I looked here first).
The basic problem was that someone had put in
const jest = require('jest');
But that is wrong (should be jest-mock instead of jest). This had worked in Node.js 10 with the line as originally put. It did not work in Node.js 14. I'm guessing that they used different versions of Jest, perhaps indirectly (other packages needed updated for 14).
const jest = require('jest-mock');
Also, in a test file, it will probably already be done for you, so you don't need to require anything. The file will run properly without that line.
Linting may give an error with that line omitted, but that can be fixed with
/* global jest */
or if you're already doing that with expect,
/* global expect, jest */
The linter seemed happy when that line appeared either after or before jest was first used. You may choose order based on readability rather than by functional requirement if your linter works the same way.
In my case it was from the module.exports.
Instead of writing
module.exports = {sum: sum};
Write
module.exports = sum;
Note: sum is a function that adds 2 numbers
In case anyone is still facing a similar issue, to resolve the failing tests I had to return a mocked function like so:
const tea = TeamMaker.makeTea();
tea(); // TypeError: tea is not a function
jest.mock('url/to/TeamMaker', () => ({ makeTea: jest.fn(() => jest.fn) })); // tests passed
For the other Jest newbies out there, if you mock multiple functions from the same package or module like this:
jest.mock(
'pathToModule',
() => ({
functionA: jest.fn(() => {
return 'contentA';
}),
})
);
jest.mock(
'pathToModule',
() => ({
functionB: jest.fn(() => {
return 'contentB';
}),
})
);
The second mock will override the first, and you'll end up with functionA is not a function.
Just combine them if they're both coming from the same module.
jest.mock(
'samePathToModule',
() => ({
functionA: jest.fn(() => {
return 'contentA';
}),
functionB: jest.fn(() => {
return 'contentB';
}),
})
);
For me, it was the jest config.
Because my project was initially in .js and was upgrade to .ts.
So the class I tested was in .ts but my jest.config.js was config for .jsfile.
See below my new config :
module.exports = {
...
collectCoverageFrom: ['src/**/*.{ts,js,jsx,mjs}'],
...
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{ts,js,jsx,mjs}',
'<rootDir>/src/**/?(*.)(spec|test).{ts,js,jsx,mjs}',
]
};
In my case, it was the way i was exporting the files that was the issue.
The file i was trying to mock was being exported like:
export const fn = () => {};
The mocked file i wrote was being exported like:
const fn = () => {};
export default fn;
Once i made sure that the mocked file was also being exported like the file to be mocked, i didn't have this issue
following is the answer which worked for me
import {testSubject} from '../testsubject';
describe('sometest',(){
it('some test scenario',()=>{
testSubject.mockReturnValue(()=>jest.fn);
})
})
I am writing my version in case it helps someone.
I was facing error while mocking firebase-admin.
I jest-mocked firebase-admin like below:
jest.mock('firebase-admin');
But I started getting following error:
firebase_admin_1.default.messaging is not a function
It was coming because in the app code, I had used firebase-admin like this:
await admin.messaging().send(message)
(message is an instance of TokenMessage)
The problem was that I was not mocking the member variables and member methods. Here it was the member method messaging and a nested member method within messaging called send. (See that messaging method is called on admin and then send is called on the value of admin.messaging()). All that was needed was to mock these like below:
jest.mock('firebase-admin', () => ({
credential: {
cert: jest.fn(),
},
initializeApp: jest.fn(),
messaging: jest.fn().mockImplementation(() => {
return {
send: jest
.fn()
.mockReturnValue('projects/name_of_project/messages/message_id'),
};
}),
}));
(Note that I have also mocked other member variables/methods as per my original requirement. You would probably need these mocks as well)
IT HAS TO BE EXPORTED.
like node function.
e.g. helper.js has myLog function
at the end,
module.exports = {
sleep,
myLog
};
now spec file can import and call them.
No tsx or jest.config.js changes...
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);
});