React and Jest: How to mock implementation of member function - reactjs

I'm doing some api fetching in my component. When doing unit testing, I'd like to mock the implementation of certain member functions:
//component.js
class Foo extends Component {
prepareData() {
getSthFromApi().then(getMoreFromApi).then(val=>this.setState({val}));
}
componentDidMount() {
this.prepareData();
}
}
//test.js
//What should this be?
Foo.prepareData = jest.fn().mockImplementation(() => {
this.setState({val:1});
})
const comp = shallow(<Foo />);
How should I do it?

You shouldn't mock your member function, instead you can mock getSthFromApi function and test both componentDidmount and prepareData together.
import { getSthFromApi } from "some-module";
jest.mock("some-module");
// in your test
getSthFromApi.resolves(1);
// here you can expect val to be 1 in your state.

The problem with this code is that Foo.prepareData is static method, while prepareData is instance method.
Since prepareData is prototype method, it can be mocked on class prototype; this is one of the reasons why prototype methods are preferable:
jest
.spyOn(Foo.prototype, 'prepareData')
.mockImplementation(function () {
this.setState({val:1});
});
Notice that mockImplementation should be provided with regular function because it doesn't need lexical this.

Related

How to partially mock a custom react hook with Jest?

I would like to mock some of my custom React hook return value with Jest.
Here is an example of what i'm trying to do :
export default function useSongPlayer() {
const playSongA = () => {...}
const playSongB = () => {...}
const playSongC = () => {...}
return {
playSongA,
playSongB,
playSongC,
}
}
Now in my test, I would like to mock only playSongA and keep the other functions real implementations. I tried several approachs, including someting with jest.requireActual('hooks/useSongPlayer') but it seems that playSongA is successfully mocked while the others are just undefined.
With something like this, i'm pretty sure to achieve what i want
export function playSongA() {...}
export function playSongB() {...}
export function playSongC() {...}
The problem seems to be the module I try to mock is a function, and I want to partially mock the result of this function.
This is possible if you create a mock that returns the original, but also intercepts the function calls and replaces one of the values.
You can do this without a Proxy, but I choose to demonstrate that as it's the most complete method to intercept any use of the actual hook.
hooks/__mocks__/useSongPlayer.js
// Get the actual hook
const useSongPlayer = jest.requireActual('hooks/useSongPlayer');
// Proxy the original hook so everything passes through except our mocked function call
const proxiedUseSongPlayer = new Proxy(useSongPlayer, {
apply(target, thisArg, argumentsList) {
const realResult = Reflect.apply(target, thisArg, argumentsList);
return {
// Return the result of the real function
...realResult,
// Override a single function with a mocked version
playSongA: jest.fn(),
};
}
});
export default proxiedUseSongPlayer;

Jest mock static method that uses Promises

When writing React app, I have one class that has several static methods, that every executes axios request and return Promise.
My component uses the class to fetch data and render them. Now I'd like to mock the whole class, not one or two methods, because all of them are in use.
Sample ServerManager.ts:
export class ServerManager {
static getUsers(): Promise<User[]> {
return Axios.get(userPath)
.then((response: Promise<AxiosResponse>): User[] => response.data);
}
// lot of similar methods
}
Sample component:
export function SomeComponent() {
const [users, setUsers] = React.useState<User[]>([]);
Promise.all([
ServerManager.getUsers().then(setUsers), //...
]).then();
return <div>
{users.length ? <div>{users.length}</div>}
</div>;
}
I'd like to fully test this component not changing its logic. Best solution would be to create MockServerManager, that imported would replace the original, but doing so makes ServerManager not defined.
EDIT:
I have created such a mock inside the __mocks__ directory:
export class MockServerManager {
static getUsers(): Promise<User[]> {
return Promise.resolve([user]);
}
// ... more methods
}
jest.mock('../../src/ServerManager', () => ({
ServerManager: MockServerManager
}));
Bot now... the promises never ends. The test is waiting for the promise unless it timeout.
Sample test:
describe('SomeComponent', (): void => {
it('renders', async (): Promise<void> => {
// when
const wrapper: ShallowWrapper = shallow(<SomeComponent/>);
await ServerManager.getUsers().then(() => {
// then
expect(wrapper).toMatchSnapshot();
});
});
});
Try importing ServerManager in MockServerManager component because it usually shows this error when we're not importing the component into our base component i.e MockServerManager or it's because you're using props in that component and not declaring the property of props.

How to use jest to mock out a private variable

I am trying to write a unit test for a function like this:
export class newClass {
private service: ServiceToMock;
constructor () {
this.service = new ServiceToMock()
}
callToTest () {
this.service.externalCall().then(()=> {
//Code to test
})
}
}
In order to test this piece of code I need to mock out service because it calls a function outside of the class but the problem is it's private.
How exactly do I mock out a private variable with jest? The class creates its own instance of it so is it even possible to mock out?
In your implementation you either import the service
implementation.js
import ServiceToMock from './serviceToMock.js';
implementation.spec.js
// import the already mocked service
import ServiceToMock from './serviceToMock.js';
import newClass from './implementation';
// auto-mock the service
jest.mock('./serviceToMock.js');
describe('newClass', () => {
describe('somMethod', () => {
beforeAll(() => {
// it is recommended to make sure
// the previous calls are cleared
// before writing assertions
ServiceToMock.prototype.externalCall.mockClear()
// jest's auto-mocking will create jest.fn()
// for each of the service's methods
// and you will be able to use methods like .mockResolvedValue
// to modify the mock behavior
ServiceToMock.prototype.externalCall.mockResolvedValue(data);
// call your method
(new newClass).someMethod();
});
it('should call ServiceToMock.externalCall', () => {
// and you can write assertions for the mocked methods
expect(ServiceToMock.prototype.externalCall).toHaveBeenCalledWith();
});
});
});
working example without Types
Or have the implementation within the file
In that case you'll have to test the both classes as that is your Unit

how to use jest to mock method of react class

I want to mock method of react class so that the unit test can run follow the mock function.
React: 16.8.6
jest: 24.8.0
Overview.js
import React from 'react';
export default class Overview extends Component{
test1(){
return {
// fetch api
}
}
test2(){
const result = this.test1();
// do other thing
return result
}
}
overview.test.js
import Overview from './index';
import { mount } from 'enzyme';
import React from 'react';
describe('test Overview',()=>{
const mockResult = {test1:'test1'};
console.info(Overview.prototype) // {}
Overview.prototype.test1=jest.fn(()=>{
return mockResult
});
it('func test2',()=>{
const wrapper = mount(<Overview/>);
const {test2} = wrapper.instance();
expect(mockResult).toEqual(test2())
})
})
Expect: run success
Actual result: run fail, because Overview.prototype cannot override or mock test1 function.
When I tried to print 'Overview.prototype', I get {}. That let me so confuse.
How to mock test1 function and why Overview cannot be overrode?
Please help me.
Try to do this:
it('func test2',()=>{
const wrapper = mount(<Overview/>);
wrapper.instance().test1 = jest.fn(() => mockResult);
expect(wrapper.instance().test2()).toEqual(mockResult);
})
There are plenty of reason to choose different approach than mocking internal methods and checking against internal methods:
it's hard and even impossible somtimes(once method under mock change state or even access variable by closure)
you stick to implementation details so even smallest refactoring(renaming internal method or property name in state) make you update tons of tests
it makes you even more confident in your component(say what if test1() stopped to call fetch()? but your tests with mocking it would not even know that component is broken)
Here is different approach: mock only external API and communicate only through public interface(for React component it's props and render() result you may access with Enzyme's methods like .find(), .filter(), .text() etc)
There are several packages that mocks global fetch() like fetch-mock but you actually may mock it on your own(don't forget to mock it with Promise not plain data):
global.fetch = jest.fn();
beforeEach(() => {
// important for mocks to keep them fresh on each test case run
global.fetch.mockClear();
});
it('renders error if fetching failed', async () => {
global.fetch.mockReturnValue(Promise.reject({}));
const wrapper = shallow(<Overview />);
wrapper.find('.some-button-to-click').props().onClick();
await Promise.resolve(); // let's wait till mocked fetch() is resolved
expect(wrapper.find('.error').text()).toEqual('Unable to load users. Try later.');
});

How to wait for setState in componentDidMount to resolve when testing with enzyme?

I am trying to test a React component that runs some asynchronous code and calls setState in componentDidMount.
Here is my react component that I want to test:
/**
*
* AsyncComponent
*
*/
import React from 'react';
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
component: null,
};
}
componentDidMount() {
this.props.component.then(component => {
this.setState({
loaded: true,
component: component.default ? component.default : component,
});
});
}
render() {
if (!this.state.loaded) {
return null;
}
const Component = this.state.component;
const { component, ...rest } = this.props;
return <Component {...rest} />;
}
}
export default AsyncComponent;
Here is the test case. I am using jest and enzyme.
import React from 'react';
import { mount } from 'enzyme';
import AsyncComponent from '../index';
const TestComponent = () => <div>Hello</div>;
describe('<AsyncComponent />', () => {
it('Should render loaded component.', () => {
const promise = Promise.resolve(TestComponent);
const rendered = mount(<AsyncComponent component={promise} />);
expect(rendered.state().loaded).toBe(true);
});
});
The test fails because state.loaded is still set to false. Is there a way I can make sure that AsyncComponent has fully loaded before calling expect?
I can get it to work if I wrap the expect assertion in a setTimeout, but that seems like a rather hacky way to do it. How should I go about doing this?
Approach with setTimeout is totally fine. Since it's a macrotask, it will be guaranteed to be called only after microtasks queue becomes empty - in other words when all the Promises are resolved and .then is processed
With this approach your test will legitimately pass after (we assume all server calls are properly mocked):
You add more calls either in sequence .then(...).then(... Another call).then(...)
You replace server call with some sync operation(reading from Redux store or local storage)
Refactor component to function version with hooks
The only thing I'd change - instead of checking state data(that would not with to function components and is fragile even to class components), I'd check .isEmptyRenderer() that must be true before timeout(so until promises are all settled) and false inside of timeout
More on macrotask/microtask difference: https://javascript.info/event-loop
[UPD] as #Estus Flask noticed below, relying on setTimeout in generic case might lead to callback hell(setTimeout after first action, then nested setTimeout to do next step etc). To avoid that we can use
await new Promise(resolve => { setImmediate(resolve); });
to flush microtasks queue. Or use tiny flush-promises package that does the same under the hood but looks lighter:
await flushPromises();
You need to notify jest about the promise either by using async/await or return the promise from the test, have a look at the docs
describe('<AsyncComponent />', () => {
it('Should render loaded component.', async() => {
const promise = Promise.resolve(TestComponent);
const rendered = mount(<AsyncComponent component={promise} />);
await promise
expect(rendered.state().loaded).toBe(true);
});
});
I encountered the same problem, and I came up with some clumsy solution, I have some function call in componentDidMount and I wanted to check if that function has been called, so that code worked for me
const loadFiltersTree = jest.fn()
const wrapper = shallow(<FilterTree loadFiltersTree={loadFiltersTree} />)
jest.useFakeTimers()
jest.runAllTimers()
setImmediate(() => {
expect(loadFiltersTree.mock.calls.length).toEqual(1)
})
Breaking a promise chain is a common antipattern. As a rule of thumb, a function that uses promises should return a resulting promise to chain, unless this causes a problem. This guarantees that there won't be race conditions when caller function chains a promise. One of reasons for this is improved testability. This also applies to lifecycle hooks like componentDidMount:
componentDidMount() {
return this.props.component.then(...)
}
Asynchronous Jest test should chain all promises in use and return a promise. async..await is a practical way to do this.
In Enzyme, shallow rendering allows to disable automatic componentDidMount call and chain a promise that lifecycle hook returns:
const wrapper = shallowMount(<AsyncComponent component={promise} />,
{ disableLifecycleMethods: true });
await wrapper.instance().componentDidMount();
expect(wrapper.state().loaded).toBe(true);
This can also be done with full rendering by spying on componentDidMount:
jest.spyOn(AsyncComponent.prototype, 'componentDidMount');
const wrapper = mount(<AsyncComponent component={promise} />);
expect(wrapper.instance().componentDidMount).toHaveBeenCalledTimes(1);
await wrapper.instance().componentDidMount.mock.results[0].value;
expect(wrapper.state().loaded).toBe(true);

Resources