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('', (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', '', [
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!
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!
Share Improve this question asked Jul 11, 2016 at 13:27 r31gN_r31gN_ 1811 gold badge1 silver badge6 bronze badges3 Answers
Reset to default 13You 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..
Give you my solution:
- enzyme 3.9.0
- enzyme-adapter-react-16 1.9.1
- react 16.8.0
- jest
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();
});
I had a look at https://www.npmjs.com/package/request and figured out that the 'body' parameter is missing from the callback.
It should look like
...
request('https://api.github.com/users', (err, res, body) => {
if (!err && res.statusCode === 200) {
this.setState({
usersList: body.slice(0)
});
}
...