Как тестировать асинхронные вызовы, сделанные в компонентеDidMount, которые задают состояние компонента React
Каков наилучший способ проверки того, что асинхронный вызов в componentDidMount
устанавливает состояние для компонента React? В контексте библиотеки, которые я использую для тестирования, Mocha
, Chai
, Enzyme
и Sinon
.
Вот пример кода:
/*
* assume a record looks like this:
* { id: number, name: string, utility: number }
*/
// asyncComponent.js
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
// assume that I'm using a library like `superagent` to make ajax calls that returns Promises
request.get('/some/url/that/returns/my/data').then((data) => {
this.setState({
records: data.records
});
});
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// NOW WHAT? This is where I'm stuck.
});
});
Ответы
Ответ 1
Игнорируя разумный совет, чтобы снова подумать о структуре, один из способов сделать это можно:
- Отметьте запрос (fx с sinon), чтобы он дал обещание для некоторых записей
- использовать функцию Enzyme
mount
- Предположим, что состояние еще не имеет ваших записей
- Используйте функцию отдыха
done
- Подождите немного (fx с
setImmediate
), это гарантирует, что ваше обещание будет разрешено.
- Повторить подтверждение на смонтированном компоненте, на этот раз проверяя, что состояние установлено
- Вызов завершенного обратного вызова для уведомления о завершении теста
Итак, короче:
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", (done) => {
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// make sure state isn't there yet
expect(wrapper.state).to.deep.equal({});
// wait one tick for the promise to resolve
setImmediate(() => {
expect(wrapper.state).do.deep.equal({ .. the expected state });
done();
});
});
});
Примечание:
У меня нет подсказки о ноке, поэтому здесь я предполагаю, что ваш код правильный
Ответ 2
IMO, на самом деле это обычная проблема, которая выглядит более сложной из-за promises и componentDidMount
:
Вы пытаетесь проверить функции, которые определяются только в рамках другой функции. т.е. вы должны разделить свои функции и проверить их индивидуально.
Компонент
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
request.get('/some/url/that/returns/my/data')
.then(this._populateState);
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_populateState(data) {
this.setState({
records: data.records
});
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
Unit Test
// asyncComponentTests.js
describe("Async Component Tests", () => {
describe("componentDidMount()", () => {
it("should GET the user data on componentDidMount", () => {
const data = {
records: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
};
const requestStub = sinon.stub(request, 'get').resolves(data);
sinon.spy(AsyncComponent.prototype, "_populateState");
mount(<AsyncComponent />);
assert(requestStub.calledOnce);
assert(AsyncComponent.prototype._populateState.calledWith(data));
});
});
describe("_populateState()", () => {
it("should populate the state with user data returned from the GET", () => {
const data = [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
];
const wrapper = shallow(<AsyncComponent />);
wrapper._populateState(data);
expect(wrapper.state).to.deep.equal(data);
});
});
});
Примечание. Я написал модульные тесты только из документации, поэтому использование shallow
, mount
, assert
и expect
может быть не лучшим образом.