I am trying to test a state
value (say: state.name
) which asynchronously changes on a button click.
Initially
state.name
is "Random Value"
state.name
changes to "peter"
from "Random Value"
button.simulate('click')
in my test
App.js
:import React, { Component } from 'react';
class App extends Component {
state = {name: "Random Value"};
render() {
return (
<div className="App">
<div>{this.state.name}</div>
<button onClick={this.handleClick}>GetData</button>
</div>
);
}
handleClick = () => {
const currentContext = this;
fetch('http://api-call/getdata')
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
// jsonData.name = "peter"
currentContext.setState({name: jsonData.name});
})
}
}
export default App;
App.test.js
describe('<App/>', () => {
it('should handle click correctly', () => {
const wrapper = shallow(<App />);
expect(wrapper.find('button').length).toBe(1);
expect(wrapper.state().name).toEqual("Random Value");
wrapper.find('button').simulate('click');
expect(wrapper.update().state().name).toEqual("peter");
});
});
// FAILED!
Expected value to equal:
"peter"
Received:
"Random Value"
All sorts of solutions out there like using async await
, setImmediate
and then wrapper.update()
and many other things. Maybe I did something wrong or missed something. Anyways I have spent an evening trying to do it. I need help from enzyme
experts.
Thanks
First you need mock fetch
somehow. Sending real request not only breaks isolation of unit-tests and adds risks of inconsistency. It's also harder to wait for response when you don't know when it may finish. There are different ways to achieve that. jest-fetch-mock
is one of them.
Also I advice you don't check for state
but rather check for render()
results.
function getName(wrapper) {
return wrapper.find('.App > div').at(0).props().children;
}
it('should handle click correctly', async () => {
fetch.mockResponseOnce(JSON.stringify({ name: '12345' }));
const wrapper = shallow(<App />);
expect(wrapper.find('button').length).toBe(1);
expect(getName(wrapper)).toEqual("Random Value");
wrapper.find('button').simulate('click');
await Promise.resolve();
expect(getName(wrapper)).toEqual("peter");
});
What's going on here. await Promise.resolve()
is just just waits until all promises already resolved are run. It means without that our mocked response will not run between button click and expect
runs.
Another way to get positive result is making handleClick()
to return Promise we can await for:
...
handleClick = () => {
const currentContext = this;
return fetch('http://api-call/getdata')
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
// jsonData.name = "peter"
currentContext.setState({name: jsonData.name});
})
}
....
it('should handle click correctly', async () => {
....
expect(getName(wrapper)).toEqual("Random Value");
await wrapper.find('button').simulate('click');
expect(getName(wrapper)).toEqual("peter");
});
or without async-await syntax:
it('should handle click correctly', () => {
....
expect(getName(wrapper)).toEqual("Random Value");
return wrapper.find('button').simulate('click').then(() => {
expect(getName(wrapper)).toEqual("peter");
});
});
But I really don't like this way since event handler have to return a Promise that it typically does not do.
More on microtasks(Promises)/macrotasks(timers, events) yuo may read here: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
More on testing async code in jest you better check their docs: https://jestjs.io/docs/en/asynchronous
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With