Reputation: 11890
Consider the following oversimplified React component. When you click the button, it makes an API call to an external URL.
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!
Upvotes: 2
Views: 253
Reputation: 39250
I think the following will do the trick:
describe('an error occurred while updating', () => {
beforeEach(() => {});
it('decrements the counter', async () => {
const promise = Promise.reject('boo');
axios.patch.mockImplementationOnce(() => promise);
const wrapper = mount(
<MyCoolButton initialCounter={99} />
);
// Click the button
wrapper.find(`.cool-button`).first().simulate('click');
//do catch().then to make sure test executes after
// component caught the rejection.
return promise.catch(x=>x).then(() => {
// Check for decrmented value
const body = wrapper.find('.container span');
expect(body).to.have.text('Counter value is: 98');
});
});
});
Here are some async examples for jest
Upvotes: 1
Reputation: 214
You need to mount the component and simulate the click event before making the assertion:
describe("an error occurred while updating", () => {
let wrapper;
beforeEach(() => {
axios.patch.mockRejectedValue("Mock error message");
wrapper = mount(<MyCoolButton initialCounter={99} />);
wrapper.find(".cool-button").simulate("click");
});
it("decrements the counter", () => {
expect(wrapper.find(".container span").text()).toEqual(
"Counter value is: 98"
);
});
});
Upvotes: 0