Jest testing that a React class method was called by componentWillMount - reactjs

I am trying to write a test to assert that my class method is being called when the componentWillMount method fires when the component renders.
I have tried the Jest documentation in addition to researching this online. From the answers I've found (including on here) there seemed to be 2 possible methods of doing this.
The first was to:
shallow render the component
create a jest.fn of the class method I want to test,
call componentWillMount using wrapper.instance().componentWIllMount
assert that the method was called once
The second was to spy on the method I'm expecting to be called:
shallow render the component
set up the spy and assign to a constant e.g. functionSpy
call componentWillMount
assert the functionSpy was called how ever many times
The refresh method definitely fires whenever the component is rendered so I just need to work out how I can reflect this in a test.
The code base I am working on is for a civil service system so have to be really careful what I disclose, hopefully this will be enough for explaining the problem I'm having..
The class is structured:
export class Search extends AnErrorComponent {
static propTypes = {
.....
};
state = {
.....
}
componentWillMount(){
this.refresh();
}
refresh = () => {
.....
} // This is the method I'm trying to test
but can't seem to access/test.
search = () => {
.....
}
//etc
render(){
return(
...
);
}
}
To test this I've tried:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
wrapper.instance().refresh = jest.fn();
wrapper.update();
wrapper.instance().componentWillMount;
expect(wrapper.instance().refresh).toHaveBeenCalledTimes(1);
});
});
The result of running this test is:
● Search component › should call the refresh method when the page loads
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
I also tried:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
const refreshSpy = spyOn(Search.prototype, 'refresh');
wrapper.instance().componentWillMount;
expect(refreshSpy).toHaveBeenCalledTimes(1);
});
});
I get the error:
● Search component › should call the refresh method when the page loads
refresh() method does not exist
This refers to the spy I tried to create.
I've double checked and I have imported the Search component in addition to the component it inherits from. I have also tried using mount instead of shallow rendering; however to make this work I had to wrap the component in a provider otherwise an error would be thrown e.g.
<provider store={store}>
<Search />
</provider>
I still got the same results after when using mount and wrapping the component in a provider. Due to the spy error I tried console logging wrapper.instance() in both tests and noted that none of the class methods are listed anywhere if this helps? Any help on this would be greatly appreciated. (This is the first question I've posted on here so hopefully this makes sense).
** Just to add, when using jest.spyOn() I get TypeError: jest.spyOn is not a function. I am using Jest 21.2.1 which I read should allow me to use jest.spyOn() as it was added in V19. **

componentWillMount is a method on the class instance, not a property. You need to call it to trigger the effect:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
wrapper.instance().refresh = jest.fn();
wrapper.update();
wrapper.instance().componentWillMount(); // Calling the method
expect(wrapper.instance().refresh).toHaveBeenCalledTimes(1);
});
});

You need to call componentWillMount and spyOn the refresh function by Mock Implementation
describe('Search component', () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
let refresh;
beforeAll(() => {
refresh = jest.spyOn(Search.prototype, 'refresh').mockImplementation(() => true);
});
it("should call the refresh method when the page loads", () => {
wrapper.instance().componentWillMount();
expect(refresh.mock.calls.length).toBe(1);
});
afterAll(() => {
refresh.mockRestore();
});
});

Related

How do I mock a const method IN a component as part of an integration test?

So, I am doing an integration test with jest & tesing-library.
So, I have my "main component" called "Product" that brings in another component, lets call it "ProductListings".
In "ProductListings", i click on a div that calls a method IN the functional component. How do I mock that, to test it was called?
test('Clicking div calls method', async () => {
render(<Product />);
// THIS div is in ProductListings component.. that is contained within Product component
const divToClickAdd = await screen.findByTestId('myDivWithOnClick')
user.click(divToClickAdd); // clicking this div fires the method
// how to do this?
expect.THE-MOCKED-FUNCTION-TO-HAVE-BEEN-CALLED
});
// this is what ProductListing looks like (psuedo). It is IN Products component, that I am integration testing.
const ProductListing = () => {
// I WANT TO MOCK THIS "fireTheAPI" in my test above.
const fireTheAPI = () => {
// do some stuff
}
return (
<div onClick={() => fireTheAPI()} data-testId="myDivWithOnClick">this is a product listing item</div>
)
}
See my issue. Bring in Parent component, that contains a "inner component, ProductListings, and I get a div from that, and click.. I need to mock that function IN it, "fireTheAPI"
btw, "Product" looks something like:
const Product = () => {
return (
<div>
<WhateverComponent />
<div>
<ProductListings /> // <--- mock a function in here
</div>
</div>
)
}
I think it depends on what your function is doing.
In this instance, if you are expecting an API call, i would suggest using nock to mock the API call.
It would look something like this
test('Your Test', () => {
const scope = nock(YOUR_URL).get(YOUR_ENDPOINT).reply(200);
const product = render(<Product />);
const divToClickAdd = await product.findByTestId('myDivWithOnClick');
act(() => {
fireEvent.click(divToClickAdd);
});
await waitFor(() => {
expect(scope.isDone()).toBe(true);
});
});
If you are expecting some DOM changes, you can go on to test that too.
IMO the integration tests should focus on the outcomes of the user actions, so rather than worrying about mocking a particular function, we should look towards attempting to mock (or test) the particular outcome of said function.

Jest: Wait for called .then of async method in componentDidMount

I am currently stuck on writing a test of my React-App.
I have an async call in my componentDidMount method and are updating the state after returning. However, I do not get this to work.
I have found several solutions and None seems to work as expected. Below is the nearest point I have come to.
App:
class App extends Component<{}, IState> {
state: IState = {
fetchedData: false
};
async componentDidMount() {
await thing.initialize();
this.test();
}
test = () => {
this.setState({ fetchedData: true })
};
render() {
return this.state.fetchedData ? (
<div>Hello</div>
) : (
<Spinner />
);
}
}
The test
it('Base test for app', async () => {
const spy = spyOn(thing, 'initialize').and.callThrough(); // just for debugging
const wrapper = await mount(<App />);
// await wrapper.instance().componentDidMount(); // With this it works, but componentDidMount is called twice.
wrapper.update();
expect(wrapper.find('Spinner').length).toBe(0);
});
Well, so...thing.initialize is called (it is an async method that fetches some stuff).
If I do explicitly call wrapper.instance().componentDidMount() then it will work, but componentDidMount will be called twice.
Here are my ideas that I have tried but None succeeded:
Spying on thing.initialize() -> I did not find out how I proceed with the test after the method has been called and finished.
Spying on App.test -> The same here
Working with promises instead of async await
At the beginning, I had an thing.initialize().then(this.test) in my componentDidMount
It can't be much, but can someone tell me which piece I am missing?
if this is integration test you better to follow awaiting approach that say Selenium use: that is, just wait until some element appears or timeout reached. How it should be coded depends on library you use(for Puppeter it should be waitForSelector).
Once it's about unit test then I suggest you different approach:
mock every single external dependencies with Promise you control(by your code it's hard to say if automatic mock will work or you need to compose mock factory but one of them or both will help)
initialize element(I mean just run shallow() or mount())
await till your mocks are resolved(with extra await, using setTimeout(... ,0) or flush-promises will work, check how microtasks/macrotasks works)
assert against element's render and check if your mocks has been called
And finally:
setting state directly
mocking/spying on internal methods
verifying against state
are all lead to unstable test since it's implementation details you should not worry about during unit-testing. And it's hard to work with them anyway.
So your test would look like:
import thing from '../thing';
import Spinner from '../../Spinner';
import flushPromises from 'flush-promises';
it('loads data and renders it', async () => {
jest.mock('../thing'); // thing.implementation is already mocked with jest.fn()
thing.initialize.mockReturnValue(Promise.resolve(/*data you expect to return*/));
const wrapper = shallow(<App />);
expect(wrapper.find(Spinner)).toHaveLength(1);
expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(0);
await flushPromises();
expect(wrapper.find(Spinner)).toHaveLength(0);
expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(1);
})
or you may test how component behaves on rejection:
import thing from '../thing';
import Spinner from '../../Spinner';
import flushPromises from 'flush-promises';
it('renders error message on loading failuer', async () => {
jest.mock('../thing'); // thing.implementation is already mocked with jest.fn()
thing.initialize.mockReturnValue(Promise.reject(/*some error data*/));
const wrapper = shallow(<App />);
expect(wrapper.find(Spinner)).toHaveLength(1);
await flushPromises();
expect(wrapper.find(Spinner)).toHaveLength(0);
expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(0);
expect(wrapper.find(SomeErrorMessage)).toHaveLength(1);
})

Return value of a mocked function does not have `then` property

I have the following async call in one of my React components:
onSubmit = (data) => {
this.props.startAddPost(data)
.then(() => {
this.props.history.push('/');
});
};
The goal here is to redirect the user to the index page only once the post has been persisted in Redux (startAddPost is an async action generator that sends the data to an external API using axios and dispatches another action that will save the new post in Redux store; the whole thing is returned, so that I can chain a then call to it in the component itself). It works in the app just fine, but I'm having trouble testing it.
import React from 'react';
import { shallow } from 'enzyme';
import { AddPost } from '../../components/AddPost';
import posts from '../fixtures/posts';
let startAddPost, history, wrapper;
beforeEach(() => {
startAddPost = jest.fn();
history = { push: jest.fn() };
wrapper = shallow(<AddPost startAddPost={startAddPost} history={history} />);
});
test('handles the onSubmit call correctly', () => {
wrapper.find('PostForm').prop('onSubmit')(posts[0]);
expect(startAddPost).toHaveBeenLastCalledWith(posts[0]);
expect(history.push).toHaveBeenLastCalledWith('/');
});
So I obviously need this test to pass, but it fails with the following output:
● handles the onSubmit call correctly
TypeError: Cannot read property 'then' of undefined
at AddPost._this.onSubmit (src/components/AddPost.js:9:37)
at Object.<anonymous> (src/tests/components/AddPost.test.js:25:46)
at process._tickCallback (internal/process/next_tick.js:109:7)
So how can I fix this? I suspect this is a problem with the test itself because everything works well in the actual app. Thank you!
Your code is not testable in the first place. You pass in a callback to the action and execute it after saving the data to the database like so,
export function createPost(values, callback) {
const request = axios.post('http://localhost:8080/api/posts', values)
.then(() => callback());
return {
type: CREATE_POST,
payload: request
};
}
The callback should be responsible for the above redirection in this case. The client code which uses the action should be like this.
onSubmit(values) {
this.props.createPost(values, () => {
this.props.history.push('/');
});
}
This makes your action much more flexible and reusable too.
Then when you test it, you can pass a stub to the action, and verify whether it is called once. Writing a quality, testable code is an art though.
The problem with your code is that the startAddPost function is a mock function which does not return a Promise, but your actual this.props.startAddPost function does return a Promise.
That's why your code works but fails when you try to test it, leading to the cannot read property.... error.
To fix this make your mocked function return a Promise like so -
beforeEach(() => {
startAddPost = jest.fn().mockReturnValueOnce(Promise.resolve())
...
});
Read more about mockReturnValueOnce here.

How to unit test functions inside componentwillmount using jest and enzyme?

I'm trying to test a function inside componentWillMount.
component
componentWillMount = () => {
const {
agents,
match
} = this.props;
this.edit = false;
this.agent = {};
if (match.params.id) {
this.edit = true;
this.agent = getAgent(agents, match.params.id);
if ("undefined" === typeof this.agent) {
push("/agents");
}
}
resetStatusMessage();
formResetError();
};
render = () => {
const { form } = this.props;
const agent = this.agent;
this.avatar = agent.avatar;
...........................
}
I'am trying to test whether the getAgent function is called.And i also need to check the resetStatusMessage() and formResetError() were called.
Tests:
it("should call getAgent when mounted", () => {
const match = {
params: {
id: "1"
}
},
agents ={
loading: false,
byId : {
1:{
firstName: "abc",
lastName: "xyz"
}
},
avatar: "avatarUrl"
};
let mockGetAgent = jest.fn();
const store = configureStore();
const wrapper = mount(
<Provider store={store}>
<AgentForm match={match} getAgent={mockGetAgent}/>
</Provider>
);
expect(wrapper).toBeDefined();
expect(mockGetAgent).toBeCalled();
});
But my test failed with this message :
TypeError: Cannot read property 'avatar' of undefined
How can i solve this issue?In my react project am using jest and enzyme for testing.am new to react and enzyme.Any help will really appreciable.
Apologies, I didn't mean you need to pass it in as a prop. This will only work if the component normally receives the getAgent function as a prop.
I'm guessing that getAgent is a function defined within the same file as your component but outside of the component itself, and that you're only exporting the component?
If this is the case, when you mount the component it will look for getAgent within its scope and try to call it. At the moment, you've created a function called mockGetAgent but the component never makes a call to mockGetAgent. I think what you need to do is call your mock getAgent and get it to return something (e.g. An object that looks like one of your agents) so that this.agent isn't undefined
Also, a couple of notes on unit testing:
you should try to test your components in isolation. Here you're testing both Provider and AgentForm at the same time, but given that they each do specific things you should just try to test they're each doing their own job.
it's not very effective to test a component by checking that every function it uses gets called. You should try to check that the job the function does has been completed. E.g. if the getAgent function gets info about agents so that it can be rendered then you should check that your wrapper contains that info, rather than checking that getAgent was called

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