everyone.
I'm having weird issues with testing a state update after an async call happening in componentDidMount
.
Here's my component code:
'use strict';
import React from 'react';
import UserComponent from './userComponent';
const request = require('request');
class UsersListComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
usersList: []
};
}
componentDidMount() {
request('https://api.github.com/users', (err, res) => {
if (!err && res.statusCode === 200) {
this.setState({
usersList: res.slice(0)
});
}
else {
console.log(err);
}
});
}
render() {
if (!this.state.usersList.length) {
return null;
}
return (
<div className="users-list">
{ this._constructUsersList() }
</div>
);
}
_constructUsersList() {
return this.state.usersList.map((user, index) => {
return (
<UserComponent
key={ index }
name={ user.name }
age={ user.age } />
);
});
}
};
export default UsersListComponent;
Now, what I'm doing in my test files (I have a setup comprised of Mocha + Chai + Sinon, all working):
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import sinon from 'sinon';
import UsersListComponent from '../src/usersListComponent';
describe('Test suite for UsersListComponent', () => {
it('Correctly updates the state after AJAX call in `componentDidMount` was made', () => {
const server = sinon.fakeServer.create();
server.respondWith('GET', 'https://api.github.com/users', [
200,
{
'Content-Type': 'application/json',
'Content-Length': 2
},
'[{ "name": "Reign", "age": 26 }]'
]);
let wrapper = mount(<UsersListComponent />);
server.respond();
server.restore();
expect(wrapper.update().state().usersList).to.be.instanceof(Array);
console.log(wrapper.update().state().usersList.length);
});
});
State does not get updated, even though I call update()
on wrapper. Length is still 0. Am I missing something here? Do I need to mock the server response in another way?
Thnx for the help!
Used mostly for data fetching and other initialization stuff componentDidMount is a nice place for async/await in React.
You just add the “async” keyword as the name of the function test, and the “await” keyword in front of the function that calls the endpoint. Using these two keywords lets Jest know that the test will be an async one. If you expect the promise to be rejected, you must use the catch method, as you did for Promises.
What to do when you want to test that a function is been called, on componentDidMount() React lifecycle Method. Basically the component code looks like this: state = { randomStateToPopulate: [] }; // Test componentDidMount componentDidMount() { this. randomFunction(); } randomFunction= () => { listRandomData().
Give you my solution:
1- Mock data service
You must mock the data service (usually something like axios or an other HTTP library).
File: __mocks__/LogDataService.js
:
let searchData = {}
const LogDataService = {
search: () => new Promise((resolve, reject) =>
process.nextTick(() => resolve(searchData))
),
setSearchData: (data) => searchData = data
}
export default LogDataService
2- Tests
File: __tests__/toto.test.js
:
jest.mock('services/LogDataService')
import LogDataService from 'services/LogDataService';
// Declare a super promise that will synchro all promises
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
it('renders without data', async () => {
// Set dummy data
LogDataService.setSearchData([])
// The component we want to test that use
// LogDataService.search method we just mock up
const component = mount(<JobsExecutionTimeline />);
// wait
await flushPromises();
// re-build the render
component.update()
// Compare to snapshot
expect(toJson(component)).toMatchSnapshot();
});
You can abstract the user list retrieval away from the react component via passing a function that returns a Promise so that instead of
componentDidMount() {
request('https://api.github.com/users', (err, res) => {
if (!err && res.statusCode === 200) {
this.setState({
usersList: res.slice(0)
});
}
else {
console.log(err);
}
});
}
Replace it with
componentDidMount() {
var comp = this;
this.props.getUsers()
.then(function(usersList) {
comp.setState({
usersList: usersList
});
})
.catch(function (err) {
console.log(err);
});
}
And inside your test mock that getUsers
function:
it('Correctly updates the state after AJAX call in `componentDidMount` was made', (done) => {
let resolveGetUsers;
let getUsers = function() {
return new Promise(function (resolve, reject) {
resolveGetUsers = resolve;
});
}
let wrapper = mount(<UsersListComponent getUsers={getUsers} />);
resolveGetUsers([{ "name": "Reign", "age": 26 }]);
// promise resolve happens in a subsequent event loop turn so move assertions inside setImmediate
setImmediate(() => {
expect(wrapper.update().state().usersList).to.be.instanceof(Array);
...
done();
});
}
Note that I have done this and it works for me (even without the wrapper.update() part) and here I tried to apply it to your code example without running it..
Also note that it should work in cases other then componentDidMount too - like having an async action triggered after clicking a button for example..
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