Consider the following oversimplified React component. When you click the button, it makes an API call to an external URL.
- If it is successful, it increments the counter
- If it is unsuccessful, it decrements the counter
import axios from 'axios';
import PropTypes from 'prop-types';
import React from 'react';
class MyCoolButton extends React.Component {
static propTypes = {
initialCounter: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
counter: props.initialCounter
}
}
onClick() {
const url = `/some/url/here`;
const data = { foo: 'bar' };
const config = { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } };
const { counter } = this.state;
return axios.patch(url, data, config)
.then((response) => { /* Success! */ this.setState({ counter: counter + 1 }); })
.catch((error) => { /* Failure :( */ this.setState({ counter: counter - 1 }); });
}
render() {
return (
<div className="container">
<span>Counter value is: {this.state.counter}</span>
<input className="cool-button" type="button" onClick={this.onClick} />
</div>
);
}
}
export default MyCoolButton;
I wanted to write a test case using Jest to ensure that when there is a failure, we are correctly decrementing the button.
I tried the following:
describe('an error occurred while updating', () => {
beforeEach(() => {
axios.patch.mockImplementationOnce(() => Promise.reject('boo'));
});
it('decrements the counter', async() => {
// NOTE: The below uses Enzyme and Chai expectation helpers
wrapper = mount(<MyCoolButton initialCounter={99} />);
// Click the button
wrapper.find(`.cool-button`).first().simulate('click');
// Check for decrmented value
const body = wrapper.find('.container span');
expect(body).to.have.text('Counter value is: 98');
});
});
The problem is that the click and subsequent state update are executed asynchronously, so we check for the failure before it has a chance to even update the component with the failure.
A lot of the examples online seem to suggest async/await which I don't understand that well. It looks like await takes a Promise as an argument, but in my case I'm simulating a click which further calls a handler which returns a Promise, so I can't await on that axios Promise to complete directly.
What's the best practice in testing here?
Thanks!