How to mock React component methods with jest and enzyme - javascript

I have a react component(this is simplified in order to demonstrate the issue):
class MyComponent extends Component {
handleNameInput = (value) => {
this.searchDish(value);
};
searchDish = (value) => {
//Do something
}
render() {
return(<div></div>)
}
}
Now I want to test that handleNameInput() calls searchDish with the provided value.
In order to do this I would like to create a jest mock function that replaces the component method.
Here is my test case so far:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
wrapper.searchDish = jest.fn();
wrapper.instance().handleNameInput('BoB');
expect(wrapper.searchDish).toBeCalledWith('BoB');
})
But all I get in the console is SyntaxError:
SyntaxError
at XMLHttpRequest.open (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:458:15)
at run_xhr (node_modules/browser-request/index.js:215:7)
at request (node_modules/browser-request/index.js:179:10)
at DishAdmin._this.searchDish (src/main/react/components/DishAdmin.js:155:68)
at DishAdmin._this.handleNameInput (src/main/react/components/DishAdmin.js:94:45)
at Object.<anonymous> (src/main/react/tests/DishAdmin.test.js:122:24)
So my question is, how do I properly mock component methods with enzyme?

The method can be mocked in this way:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
wrapper.instance().searchDish = jest.fn();
wrapper.update();
wrapper.instance().handleNameInput('BoB');
expect(wrapper.instance().searchDish).toBeCalledWith('BoB');
})
You also need to call .update on the wrapper of the tested component in order to register the mock function properly.
The syntax error was coming from the wrong assingment (you need to assign the method to the instance). My other problems were coming from not calling .update() after mocking the method.

Needs to be replaced wrapper.update(); with wrapper.instance().forceUpdate();

#Miha's answer worked with a small change:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
const searchDishMock = jest.fn();
wrapper.instance().searchDish = searchDishMock;
wrapper.update();
wrapper.instance().handleNameInput('BoB');
expect(searchDishMock).toBeCalledWith('BoB');
})

Related

In Jest, how to test a function declared inside a function?

I am trying to test a function that is declared inside another function(Parent). I tried a few things but was not able to test that. I am using react library and JEST testing framework in the project.
Here is the service file (service.ts) function which I am trying to test:
import { useThisHook } from './base-hooks';
export function someFunction(param: string) {
const [
resolveFunction,
,
{ loading, error },
] = useThisHook();
const childFun = (param : number) => {
resolveFunction(someArguments);
}
return{
someValue: childFun
}
}
Here is the spec file code:
import * as SomeHooks from './base-hooks';
import * as Service from './service';
describe('Service function', () => {
beforeEach(() => {
jest.spyOn(SomeHooks, 'useThisHook').mockReturnValue(
{
//Some Value
}
);
});
test('Some function test', () => {
const someFunctionResponse = Service.someFunction('string');
expect(someFunctionResponse).toEqual({
someValue: expect.any(Function),
});
});
});
Till here it is fine. Tests are also getting passed but the problem is that childFn is not getting the coverage and that's what my requirement is. I am new to React and Jest. I am not able to understand how can I achieve that. I tried many things but didn't succeed.
Your childFn is not being called, that's why it's not getting coverage.
You could either refactor the whole childFn to a new hook and test it individually with something like react-hooks-testing-library.
Or you could separate the childFn and declarate it outside the someFunction scope pass the resolveFunction and then test it.
You only get coverage if the code is actually called, not when it is declared.

Jest, mocking ES6 class constructor not working as intended

I have code that I am trying to test with Jest. I have an ES6 class, let's call it ClassA. Within my test file, I am mocking ClassA as follows:
const mockGetCheapestProductAllPages = jest.fn()
const ClassA = require('../src/ClassA/ClassA')
jest.mock('../src/ClassA/ClassA', () => {
return jest.fn().mockImplementation(() => {
return { getCheapestProductAllPages: mockGetCheapestProductAllPages }
})
})
This syntax works just fine for mocking the class. I am able to spy on getCheapestProductAllPages just fine, and mock return values from it, etc. The problem is, I cannot spy on the constructor for some reason. In the Jest documentation on mocking ES6 classes, it is implied that you can just reference the imported class in order to spy on the constructor, like so:
expect(ClassA).toHaveBeenCalled()
Unfortunately, this is not working for me. I know the constructor is being invoked in my test, but Jest tells me it is invoked 0 times. Any suggestions/anything look wrong? Tried to only share relevant code but please let me know if there is anything else you need to see. Thanks!
Ended up declaring my Jest mockImplementation outside of my mock and referencing it in my tests like so:
// top of test file
const mockGetCheapestProductAllPages = jest.fn()
const mockClassAImplementation = jest.fn().mockImplementation(() => {
return { getCheapestProductAllPages: mockGetCheapestProductAllPages }
})
const ClassA = require('../src/ClassA/ClassA')
jest.mock('../src/ClassA/ClassA', () => {
return mockClassAImplementation
})
// tests
test('Expect ClassA constructor to be called', () => {
// invoke method that invokes ClassA constructor
// this works!
expect(mockClassAImplementation).toHaveBeenCalled()
})

In Enzyme, how to get a function from the props of a functional component?

I am writing unit tests for my react project using Jest and Enzyme.
As shown below, I passed a function named updateUser to the to-be-tested component EditCard via props.
describe('The EditCard screen', () => {
let wrapper;
beforeEach(() => {
const defaultProps: Partial<EditCardProps> = {
toggleEditing: jest.fn(),
user: mockUsers[0],
updateUser: jest.fn(), // passes this function to the "EditCard" component via props
showSnackbar: jest.fn(),
};
wrapper = shallow(<EditCard {...(defaultProps as EditCardProps)} />);
});
Then I want to test how many times it was called after simulating a click on a button.
it('should match the snapshot when the "Name" textfield is not filled and the "submit" button is clicked', () => {
wrapper.find('#Name').simulate('change', { target: { value: null } });
wrapper.find('#submit').simulate('click');
// Try to get the "updateUser" function from the props, but get "undefined".
expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
});
But I got the error shown below:
Matcher error: received value must be a mock or spy function
Received has value: undefined
24 | wrapper.find('#Name').simulate('change', { target: { value: null } });
25 | wrapper.find('#submit').simulate('click');
> 26 | expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
Could someone tell me where I did wrong? Why I cannot get the function from the props and undefined was returned?
Thanks in advance!
A few tweaks to your code should have it working...
import * as React from "react";
import { mount, ReactWrapper } from "enzyme";
import EditCard from "../path/to/EditCard";
/*
I'd recommend defining jest fns here to make them easier to reference anywhere
within the tests below; otherwise, it'll have to referenced via
'defaultProps.updateUser', 'defaultProps.showSnackbar', ...etc.
Using 'const' here allows us to define these variables within the
module's closure -- in short, only accessible within these tests and NOT
globally accessible (from other file tests).
*/
const showSnackbar = jest.fn();
const toggleEditing = jest.fn();
const updateUser = jest.fn();
/*
if the EditCard component is properly typed, then you shouldn't need to
add types to this 'defaultProps' object
*/
const defaultProps = {
showSnackbar,
toggleEditing,
updateUser,
user: mockUsers[0]
};
describe('The EditCard screen', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
/*
I'd recommend mount over shallow because child components can be
deeply nested and require multiple .dive calls; however, if
you know the child components of "EditCard" are just simple JSX elements,
then shallow will be fine
*/
wrapper = mount(<EditCard {...defaultProps} />);
});
it("should not call 'updateUser' when the form is submitted with an empty '#Name' field", () => {
/*
I'm not sure what simulating "null" does for this input, but assuming this
input is required you should at least pass a string value -- assuming
"#Name" input is of type 'text' | 'password' | 'email' => string and
not a number. On a related note, if it's required, then simply remove this
code as it doesn't do much for the test.
*/
// wrapper.find('#Name').simulate('change', { target: { value: "" } });
/*
I'm assuming this then simulates a form submit. Unfortunately,
pressing the submit button won't work. Instead you'll have to
simulate a form submit. This is a limitation of Enzyme and last I
checked hasn't been/can't be fixed.
*/
wrapper.find('form').simulate('submit');
/*
it should then NOT call the jest.fn() "updateUser" when submitted since
'#Name' is empty. Notice that we're referencing 'updateUser' -- the jest fn
defined above -- and not the wrapper.prop fn.
*/
expect(updateUser).not.toHaveBeenCalled();
});
// include other tests...
});
Here's a working example (click the Tests tab to run tests):

Nested describe and behaviour of dynamically created "it"s

I have nested describes in my tests and as usual I am using some beforeEach and before in describes. And one of my describe function calls helper function which creates dynamic tests (DRY). And mocha runs description of nested describe before beforeEach method. And my dynamically created it has comp as undefined.
const checkProps = (comp, propName, expectedvalue) => {
it(`${comp} should have ${propName} equal to ${expectedvalue}`, () => {
expect(comp.prop(propName)).to.equal(expectedvalue);
});
};
describe('Component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps(wrapper, 'title', 'SomeTitle');
});
});
What is the best way todo it? Thanks in advance.
What happens
The Mocha Run Cycle runs all describe callback functions first (...which is also true for other testing frameworks such as Jest and Jasmine).
Then it runs the before hooks, then beforeEach hooks, and finally the it callbacks.
So checkProps runs as part of running the initial describe callbacks, and at that point wrapper is undefined, so as you have noticed the test description says undefined should have....
The beforeEach hook runs before the it callback function runs...but it redefines wrapper so when the it callback runs comp is still undefined and the test fails:
1) Component
prop checking
undefined should have title equal to SomeTitle:
TypeError: Cannot read property 'prop' of undefined
at Context.prop (test/code.test.js:15:19)
Solution
A couple of things need to be changed:
The component name needs to be available when it runs and at that point wrapper doesn't exist yet so you'll have to pass the name yourself.
If you pass an object to checkProps then you can set a wrapper property on the object during beforeEach and access that wrapper property within your test since the object is never redefined.
Here is a working test that should get you closer to what you are trying to do:
import * as React from 'react';
import { shallow } from 'enzyme';
const MyComponent = () => (<div title="SomeTitle">some text</div>);
const checkProps = (name, obj, propName, expectedvalue) => {
it(`${name} should have ${propName} equal to ${expectedvalue}`, () => {
expect(obj.wrapper.prop(propName)).to.equal(expectedvalue); // Success!
});
};
describe('Component', () => {
const obj = {};
beforeEach(() => {
obj.wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps('MyComponent', obj, 'title', 'SomeTitle');
});
});

Mock dependency in Jest with TypeScript

When testing a module that has a dependency in a different file and assigning that module to be a jest.mock, TypeScript gives an error that the method mockReturnThisOnce (or any other jest.mock method) does not exist on the dependency, this is because it is previously typed.
What is the proper way to get TypeScript to inherit the types from jest.mock?
Here is a quick example.
Dependency
const myDep = (name: string) => name;
export default myDep;
test.ts
import * as dep from '../depenendency';
jest.mock('../dependency');
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
dep.default.mockReturnValueOnce('return')
}
I feel like this is a very common use case and not sure how to properly type this.
You can use type casting and your test.ts should look like this:
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
mockedDependency.mockReturnValueOnce('return');
});
TS transpiler is not aware that jest.mock('../dependency'); changes type of dep thus you have to use type casting. As imported dep is not a type definition you have to get its type with typeof dep.default.
Here are some other useful patterns I've found during my work with Jest and TS
When imported element is a class then you don't have to use typeof for example:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
This solution is also useful when you have to mock some node native modules:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
In case you don't want to use jest automatic mock and prefer create manual one
import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';
const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
// implementation
}));
it('Should throw an error when calling playSomethingCool', () => {
const testedClass = new TestedClass(testedClassDependencyMock());
});
testedClassDependencyMock() creates mocked object instance
TestedClassDependency can be either class or type or interface
Use the mocked helper
like explained here
// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')
// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)
test('deep', () => {
// there will be no TS error here, and you'll have completion in modern IDEs
mockedFoo.a.b.c.hello('me')
// same here
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})
test('direct', () => {
foo.name()
// here only foo.name is mocked (or its methods if it's an object)
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})
There are two solutions tested for TypeScript version 3.x and 4.x, both are casting desired function
1) Use jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) Use jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
There is no difference between these two solutions. The second one is shorter and I would therefore suggest using that one.
Both casting solutions allows to call any jest mock function on mockMyFunction like mockReturnValue or mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction can be used normally for expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);
I use the pattern from #types/jest/index.d.ts just above the type def for Mocked (line 515):
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
Cast as jest.Mock
Simply casting the function to jest.Mock should do the trick:
(dep.default as jest.Mock).mockReturnValueOnce('return')
Use as jest.Mock and nothing else
The most concise way of mocking a module exported as default in ts-jest that I can think of really boils down to casting the module as jest.Mock.
Code:
import myDep from '../dependency' // No `* as` here
jest.mock('../dependency')
it('does what I need', () => {
// Only diff with pure JavaScript is the presence of `as jest.Mock`
(myDep as jest.Mock).mockReturnValueOnce('return')
// Call function that calls the mocked module here
// Notice there's no reference to `.default` below
expect(myDep).toHaveBeenCalled()
})
Benefits:
does not require referring to the default property anywhere in the test code - you reference the actual exported function name instead,
you can use the same technique for mocking named exports,
no * as in the import statement,
no complex casting using the typeof keyword,
no extra dependencies like mocked.
The latest jest allows you to do this very easily with jest.mocked
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = jest.mocked(dep);
it('should do what I need', () => {
mockedDependency.mockReturnValueOnce('return');
});
Here's what I did with jest#24.8.0 and ts-jest#24.0.2:
source:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
test:
import { OAuth } from '../src/to/the/OAuth'
jest.mock('../src/utils/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
describe('createMeeting', () => {
test('should call conferenceLoginBuild when not login', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
// Other tests
});
});
This is how to mock a non-default class and it's static methods:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
Here should be some type conversion from the type of your class to jest.MockedClass or something like that. But it always ends up with errors. So I just used it directly, and it worked.
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
But, if it's a function, you can mock it and do the type conversation.
jest.mock('../src/to/the/Conference', () => ({
conferenceSuccessDataBuild: jest.fn(),
conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as
jest.MockedFunction<
typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as
jest.MockedFunction<
typeof conferenceSuccessDataBuild
>;
As of Jest 24.9.0 here is how you can mock and correctly type both your Class/Object/function and Jest properties.
jest.MockedFunction
jest.MockedClass
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.
import foo from 'foo';
jest.mock('foo');
const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;
mockedFoo.mockResolvedValue('mockResult');
// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');
As you can see, you can either manually cast what you need or you'll need something to traverse all foo's properties/methods to type/cast everything.
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
import foo from 'foo';
jest.mock('foo');
const mockedFoo = jest.mocked(foo, true);
mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed
I have found this in #types/jest:
/**
* Wrap a function with mock definitions
*
* #example
*
* import { myFunction } from "./library";
* jest.mock("./library");
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
Note: When you do const mockMyFunction = myFunction and then something like mockFunction.mockReturnValue('foo'), you're a changing myFunction as well.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
The top rated solution by Artur Górski does not work with the last TS and Jest.
Use MockedClass
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
A recent library solves this problem with a babel plugin: https://github.com/userlike/joke
Example:
import { mock, mockSome } from 'userlike/joke';
const dep = mock(import('./dependency'));
// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({
thisIsAMock: jest.fn()
}));
it('should do what I need', () => {
dep.mockReturnValueOnce('return');
}
Be aware that dep and mockReturnValueOnce are fully type safe. On top, tsserver is aware that depencency was imported and was assigned to dep so all automatic refactorings that tsserver supports will work too.
Note: I maintain the library.
This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
Make sure you require './dependency' rather than the mock directly, or you will get two different instantiations.
For me this was enough:
let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
return {
add: async (item: queueItemType, ..._args: any) => {
// then we can use the item that would be pushed to the queue in our tests
itemQ = item
return new Promise(resolve => {
resolve('Mocked')
})
},
}
})
Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.
With TypeScript 2.8 we can do like this with ReturnType:
import * as dep from "./depenendency"
jest.mock("./dependency")
const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default
it("should do what I need", () => {
mockedDependency.mockReturnValueOnce("return")
})

Categories

Resources