What is the best way to test that an async call within ponentDidMount
sets the state for a React ponent? For context, the libraries I'm using for testing are Mocha
, Chai
, Enzyme
, and Sinon
.
Here's an example code:
/*
* 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: []
};
}
ponentDidMount() {
// 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_ponent">
{ 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 ponentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("")
.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.
});
});
What is the best way to test that an async call within ponentDidMount
sets the state for a React ponent? For context, the libraries I'm using for testing are Mocha
, Chai
, Enzyme
, and Sinon
.
Here's an example code:
/*
* 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: []
};
}
ponentDidMount() {
// 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_ponent">
{ 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 ponentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("http://some.url.")
.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.
});
});
Share
Improve this question
edited Aug 3, 2016 at 22:07
wmock
asked Aug 3, 2016 at 20:20
wmockwmock
5,5025 gold badges43 silver badges62 bronze badges
7
-
Wouldn't you just assert that your state updated correctly? I'm not all that familiar with using Enzyme and not using the
shallow()
api, but with shallow-rendered ponents you can assume that the state update is synchronous. – Michael Parker Commented Aug 3, 2016 at 21:54 -
My question is more focused on the async part of this - if I were to assert the state initially after render,
records
would be the empty array. Instead, I'm hoping to make the assertion after the promise inponentDidMount
sets the state to a non-empty array. – wmock Commented Aug 3, 2016 at 22:05 - 2 In reality, it is best practice to move that functionality out of the ponent so it can be tested separately and you can mock it for testing the ponent. But you could always use setTimeout. You have control over nock so you can be pretty sure about how long the response will take. – aray12 Commented Aug 4, 2016 at 0:34
- Just so I understand, when you're using nock, you can control how long it takes before the response es back? And assuming thats the case, then you can make an assertion within the setTimeout after that amount of time has elapsed? – wmock Commented Aug 4, 2016 at 5:27
- 1 You can and it should work, but the suggestion to use a Flux architecture (i.e. redux) is also a good one. Your ponent's will be "dumb" in the sense that they just render what you give them, making testing easier (and synchronous). – ivarni Commented Aug 4, 2016 at 14:43
3 Answers
Reset to default 3So, what you are really trying to test is that based on some mock data it "should render correctly ...".
As some people pointed out, a good way to achieve that is by placing the data fetching logic into a separate container and have a "dumb" presentation ponent that only knows how to render props
.
Here is how to do that: (I had to modify it a bit for Typescript with Tslint, but you'll get the idea)
export interface Props {
// tslint:disable-next-line:no-any
records: Array<any>;
}
// "dumb" Component that converts props into presentation
class MyComponent extends React.Component<Props> {
// tslint:disable-next-line:no-any
constructor(props: Props) {
super(props);
}
render() {
return (
<div className="async_ponent">
{this._renderList()}
</div>
);
}
_renderList() {
// tslint:disable-next-line:no-any
return this.props.records.map((record: any) => {
return (
<div className="record" key={record.name}>
<p>{record.name}</p>
<p>{record.utility}</p>
</div>
);
});
}
}
// Container class with the async data loading
class MyAsyncContainer extends React.Component<{}, Props> {
constructor(props: Props) {
super(props);
this.state = {
records: []
};
}
ponentDidMount() {
fetch('/some/url/that/returns/my/data')
.then((response) => response.json())
.then((data) => {
this.setState({
records: data.records
});
});
}
// render the "dumb" ponent and set its props
render() {
return (<MyComponent records={this.state.records}/>);
}
}
Now you can test MyComponent
rendering by giving your mock data as props.
Ignoring the, sane, advice to think again about the structure, one way to go about this could be:
- Mock the request (fx with sinon), to make it return a promise for some records
- use Enzyme's
mount
function - Assert that the state to not have your records yet
- Have your rest function use
done
callback - Wait a bit (fx with
setImmediate
), this will make sure your promise is resolved - Assert on the mounted ponent again, this time checking that the state was set
- Call your done callback to notify that the test has pleted
So, in short:
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in ponentDidMount executes", (done) => {
nock("http://some.url.")
.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();
});
});
});
Note:
I have no clue about nock, so here I assume your code is correct
IMO, this is actually a mon issue which appears more plicated because of promises and ponentDidMount
:
You're trying to test a functions which are only defined within the scope of another function. i.e. You should split your functions out and test them individually.
Component
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
ponentDidMount() {
request.get('/some/url/that/returns/my/data')
.then(this._populateState);
}
render() {
return (
<div className="async_ponent">
{ 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("ponentDidMount()", () => {
it("should GET the user data on ponentDidMount", () => {
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);
});
});
});
Note: I've written the unit tests from documentation alone, so the use of shallow
, mount
, assert
, and expect
might not be best practices.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744218899a4563684.html
评论列表(0条)