Reputation: 421
I have a simple React Parent component that has a single state value called counter. Under the Parent component I then have a single Child component that is a button with an onClick hander that calls back to the Parent to update the state.
import React, { Component } from 'react';
import axios from 'axios'
class Parent extends Component {
state = {counter: 0}
onClick = () => {
console.log("Button clicked")
this.setState(prevState => ({
counter: prevState.counter + 1
}))
}
render() {
return (
<div>
<Child onClick={this.onClick} action="Click"/>
</div>
);
}
}
class Child extends Component {
render() {
return (
<button onClick={this.props.onClick}>{this.props.action}</button>
)
}
}
export default Parent
This tests fine using Jest with the following test:
import React from 'react';
import {mount, shallow} from 'enzyme';
import Parent from '../Problem';
it('handles Click event', () => {
const wrapper = mount(<Parent />)
expect(wrapper.state().counter).toEqual(0)
console.log(wrapper.debug())
const button = wrapper.find('button').at(0);
button.simulate('click');
expect(wrapper.state().counter).toEqual(1)
console.log(wrapper.state().counter)
});
If I change the code to the following to simulate an async update:
import React, { Component } from 'react';
import axios from 'axios'
class Parent extends Component {
state = {counter: 0}
onClickAsync = () => {
console.log("Async Button clicked")
axios.get(`https://api.github.com/users/smitp33`)
.then(resp => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
}
)
}
render() {
return (
<div>
<Child onClick={this.onClickAsync} action="Click"/>
</div>
);
}
}
class Child extends Component {
render() {
return (
<button onClick={this.props.onClick}>{this.props.action}</button>
)
}
}
export default Parent
... then the same test fails as it does not appear that the counter has been updated.
handles Click event
expect(received).toEqual(expected)
Expected value to equal:
1
Received:
0
at Object.<anonymous>.it (src/__tests__/Problem.test.js:14:35)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at process._tickCallback (internal/process/next_tick.js:109:7)
I have added a callback to the setState function to prove that the state has indeed changed and it definitely has, so I believe the issue is around the Jest test simply completing before the async component call has completed.
I have read various articles and the Jest docs and tried all sorts of things here to try and get the test to pass, but have not succeeded thus far, so I thought I would post here to see if anyone could provide some clear advice on how exactly this should be approached.
Details on exact dependancies are as follows:
"dependencies": {
"axios": "^0.17.0",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"jest-enzyme": "^4.0.1",
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-circular-progressbar": "^0.5.0",
"react-dom": "^16.0.0",
"react-percentage-circle": "^1.1.3",
"react-scripts": "1.0.15"
}
This is all done in a vanilla 'create-react-app' setup with the following defined in the setupTests.js:
import Enzyme from "enzyme";
import 'jest-enzyme';
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
global.requestAnimationFrame = function(callback) {
setTimeout(callback, 0);
};
Upvotes: 0
Views: 1487
Reputation: 421
With the details provided above I managed to get this to work as follows:
Instead of testing the async click as part of a simulation I chose to test the function in isolation.
By modifying the click event to actually return something ... in this case a Promise I was then able to make proper use of async and await in the test.
Modified Code:
onClickAsync = () => {
console.log("Async Button clicked")
return (
axios.get(`https://api.github.com/users/smitp33`)
.then(resp => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
}
)
)
}
Modified test:
test('calls Async Click function', async () => {
const wrapper = mount(<Parent />)
console.log(wrapper.state().counter)
await wrapper.instance().onClickAsync()
console.log(wrapper.state().counter)
expect(wrapper.state().counter).toEqual(1)
});
Upvotes: 0