I’d like to come back to where we can re-discuss about JavaScript testing, since unit test, tdd and bdd are so popular among us developers. I believe most developers once ran into a situation where we spent more time on testing than developing component itself. I should say no testing is painless. Last week, I got an interview asking me what to test during development. It provokes me thinking this question, and more further to how to mitigate this pain and what is important in tesing.
You created a component, what’s next? From my point of view, there are some aspects of testing we should pay the attention to.
- inputs and outputs
- appearance
- behavior
- context
- side effects
Take React testing for example, with testing tools jest, enzyme, react-test-renderer. Let’s create a component named Autocomplete. Here I just show the key parf of my code below:
//...
const addr = "http://localhost:3001/";
export default class extends React.Component {
state = {
//...
};
cbHandler(item) {
//setState with setTimeout, because it's called in children
}
componentDidMount() {
console.log('componentDidMount')
//fetch ...
}
inputHandler(event) {
let value = event.target.value;
//... fetch
}
keypressHandler(event) {
if (event.charCode === 13) {
//... fetch
}
}
render() {
return (
<StyledComp>
<input
type="text"
value={this.state.value}
onInput={this.inputHandler.bind(this)}
onKeyPress={this.keypressHandler.bind(this)}
/>
<Suggestions data={this.state.data} cb={this.cbHandler.bind(this)} />
</StyledComp>
);
}
}
//...
export default class extends React.Component {
clickHandler(item) {
console.log("clickHandler");
this.props.cb(item);
}
render() {
const { data } = this.props;
return (
//...
<ul>
{data
? data.map(e => {
return (
<li key={e.id} onClick={this.clickHandler.bind(this, e)}>
{e.name}
</li>
);
})
: ""}
</ul>
);
}
}
Writing Test suite
./components/__test__/AutoComp.test.js
testing appearance with jest snapshot:
describe("Autocomplete appearance", () => {
it("renders correctly", () => {
const tree = renderer.create(<Autocomplete />).toJSON();
expect(tree).toMatchSnapshot();
});
});
it will output a .snap file for us, ./components/__test__/__snapshots/AutoComp.test.js.snap
So, every time you call expect(tree).toMatchSnapshot(); you want to compare the looks of the rendering component with the snapshot in this file.
testing behavior with enzyme
describe("Autocomplete behavior", () => {
it("behave correctly", () => {
const w = mount(<Autocomplete />);
expect(w.find("input")).toHaveLength(1);
w.find("input").simulate("input", { target: { value: "abc" } });
expect(w.state("value")).toEqual("abc");
});
});
We should avoid testing with context, but if we can’t, just construct one!
If you using Redux to manage your shared states, redux-mock-store is certainly a good choice!
dealing with side effects, like networking fetch.
To simplify, we can write a function to fake fetch by using jest.fn, which helps us to spy function call on parameters, results and so on.
<br />global.fetch = jest.fn(url => {
let resp = {
...
};
//Promise.resolve({ blob: blob => resp })
return Promise.resolve({ json: json => resp });
});
Also, if you don’t want to write this, you could turn to jest-fetch-mock, which allows you to easily mock your fetch calls and return the response you need to fake the HTTP requests.
To install, npm install –save-dev jest-fetch-mock
Then add global.fetch = require(‘jest-fetch-mock’)
to rootDir/setupTests.js to setup the fetch API on global context.
Use fetch.mockResponse(body, init) to mock http reponse.
remember to reset after a mocking, fetch.resetMocks();
JSON Server
Lastly, if you wanna test real requesting, I recommend to use JSON Server for testing REST API, with which you can get http response in JSON format.
Get a full fake REST API with zero coding in less than 30 seconds
GET /posts
GET /posts/1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
After installation, start like this:
json-server -w db.json
But here I want to fake massive outputs, so I use a js file to generate outputs, and then start using this command:
json-server -p 3001 -w data.js
After launch, there should be output like this:
Resources
http://localhost:3001/users
http://localhost:3001/meta
Home
http://localhost:3001