Reputation: 595
I'm a novice at react and any javascript testing frameworks.
I have a simple component that retrieves an item from the API and shows them to the screen.
The function getItems() is called from componentWillMount.
Is it possible to wait until getItems() has finished before making my assertions?
ItemDetails.js
class ItemDetails extends Component {
constructor(props) {
super(props);
this.state = {
details: ''
}
}
componentWillMount() {
this.getItem();
}
getItem() {
const itemId = this.props.match.params.id;
fetch(`/api/items/${itemId}`)
.then(res => res.json())
.then(details => this.setState({ details }));
}
render() {
const details = this.state.details;
return (
<div>
<h1>{details.title}</h1>
...
</div>
);
}
}
export default ItemDetails;
ItemDetails.test.js
describe('ItemDetails', () => {
it('should render a div with title', () => {
const details = {
_id: 1,
title: 'ItemName'
};
fetch.mockResponseOnce(JSON.stringify(details));
const wrapper = mount(<ItemDetails match={{ params: {id: 1} }} />);
expect(wrapper.find('div').find('h1').text()).toBe('ItemName');
});
});
Upvotes: 7
Views: 9422
Reputation: 474
The answer above works, but it invites to test implementation details:
.instance()
) and calling update()
are smells of tests that know too much about the way the code under test works in my opinion, it is not testing a behavior, it is testing an implementationWhat you really want is to wait on all promises to be resolved, without accessing a handle to those promises (that would break privacy). Try this
const wait = () => new Promise(resolve => setTimeout(resolve));
it('does something', () => {
renderFnThatCallsAPromiseInternally();
return wait().then(() => {
expect(somethingDependentOnPromiseExecution).to.be.present();
});
});
This will wait for all inner promises to resolve, provided wait
is called after the code that enqueues promises (rendering the component) is called. The reason lies in how the JS event loop works, it's finicky, but very well explained by Jake Archibald in this talk: https://www.youtube.com/watch?v=cCOL7MC4Pl0.
Hope that helps.
Upvotes: 9
Reputation: 36179
Could you try:
describe('ItemDetails', () => {
it('should render a div with title', () => {
const details = {
_id: 1,
title: 'ItemName'
};
fetch.mockResponseOnce(JSON.stringify(details));
const wrapper = shallow(<ItemDetails match={{ params: {id: 1} }} />);
// manually call function
wrapper.instance().getItem();
// update to re-render component
wrapper.update();
expect(wrapper.find('div').find('h1').text()).toBe('ItemName');
});
});
If it doesn't help I think you will need to return Promise from your function (base on this example):
getItem() {
const itemId = this.props.match.params.id;
return fetch(`/api/items/${itemId}`)
.then(res => res.json())
.then(details => this.setState({ details }));
}
describe('ItemDetails', () => {
it('should render a div with title', () => {
const details = {
_id: 1,
title: 'ItemName'
};
fetch.mockResponse(JSON.stringify(details)); //response gets called twice
const wrapper = mount(<ItemDetails match={{ params: {id: 1} }} />);
// return Promise so Jest will wait until it's finished
return wrapper.instance().getItem().then(() => {
wrapper.update();
}).then(() => {
expect(wrapper.find('div').find('h1').text()).toBe('ItemName');
})
});
});
Upvotes: 3