Let’s Add Some Code to Test
NOTE: the starting point code for this section is located here: https://github.com/RyanAtViceSoftware/react-testing-best-practices/tree/step-1-create-react-app-setup
We are going to take advantage of JSONplaceholder which is a dummy blog API that’s great for trying out client side code. Let’s update our create-react-app based app to write out a couple of dummy blog post that we will borrow from http://jsonplaceholder.typicode.com/posts. To do this we will update our app.js
file as shown below.
Update the App component
Let’s get our App
component as shown below.
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
componentDidMount() {
getPosts()
.then(posts => this.setState({ posts: posts}));
}
render() {
return (
<div className="App">
{!this.state.posts.length && <h3>Loading...</h3>}
<ul>
{this.state.posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
</div>
);
}
}
In our App.componentDidmount()
method we are on line 11 calling GetPosts
, a data access component, to get posts from the server and then in the App.render()
method on line 20 we are using use this.state.posts.map()
to write the posts returned by getPosts()
out to the user.
Let’s add our data access components
To simulate getting data getPosts()
method is simply calling our http.get()
method and passing the post resources URL, /posts
, along with some dummy data to be returned as shown below.
function getPosts() {
return http.get('/posts', null, { dummyData: getDummyPosts() });
}
In our http.get()
method currently we just fakes async calls using setTimeout
and we allow for specifying dummyData to be returned in the third parameter parameter as shown below.
export const http = {
get: (url, config, { dummyData } = {}) => new Promise(resolve =>
setTimeout(() => {
resolve(dummyData);
}, 1000
)
)
};
Note: we are making this third parameter a destructed object which allows us to easily add other optional configurations later (like an error to return).
And lastly our getDummyPosts()
method simply returns a few posts from http://jsonplaceholder.typicode.com/posts that we copied and pasted in.
function getDummyPosts() {
return [
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
}
];
}
At this point if we run our code using npm start
we should see something similar to what is shown below.
Let’s Add Our First New Test
Now let’s open app.test.js
and update it as shown below.
import React from 'react';
import ReactDOM from 'react-dom';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import App from './App';
Enzyme.configure({ adapter: new Adapter() });
describe('Given we load our app ', () => {
it('Then renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
it('Then it shows a loading indicator', () => {
const app = mount(<App/>);
expect(app.getElements().length).toBeTruthy();
const loadingIndicator = app.find('h3');
expect(loadingIndicator.getElements().length).toBeTruthy();
expect(loadingIndicator.getElements()[0].props.children).toBe('Loading...');
});
});
If we save and then run npm test
from a console in the root of our repository we should see the output below.
What we have done above is update our test to use Gherkin Syntax and we’ve nested our existing test in a describe block. This makes it so that we are now testing two scenarios and those are clearly describe using Gherkin in our test output and we get some nice nesting by taking advantage of the describe() method from Jest. The two things we are currently testing map nicely to our requirements:
- That the app doesn’t blow up when it’s loaded in the DOM
- That we are shown a
Loading...
indicator when the app is first loaded
To accomplish this we added one new test which is shown below.
it('Then it shows a loading indicator', () => {
const app = mount(<App/>);
expect(app.getElements().length).toBeTruthy();
const loadingIndicator = app.find('h3');
expect(loadingIndicator.getElements().length).toBeTruthy();
expect(loadingIndicator.getElements()[0].props.children).toBe('Loading...');
});
What this test method does is first on line 2 mount the App
into a JSDOM virtual DOM using Enzyme as shown below.
const app = mount(<App/>);
NOTE:Enzyme is a wrapper around React Test Utils created by Air BnB because the API for React Test Utils wasn’t the best. They’ve improved the API and now you can easily mount a component like the code below:
To get Enzyme to work we have to first configure Enzyme using the following code:
Enzyme.configure({ adapter: new Adapter() });
which uses a React 0.16 adapter which is imported with the following code:
import Adapter from 'enzyme-adapter-react-16';
Now that we have our App
component mounted in the virtual DOM and we have a ReactWrapper instance in our app
variable we can easily query for components using an Enzyme Selector syntax which is very similar to CSS or JQuery selector syntax with a few other goodies. Below we are querying for a descendant of our App
component that is an h3
component.
const loadingIndicator = app.find('h3');
This will return a ReactWrapper
instance that we can then call the getElements()
method on to get an array of ReactWrapper
s that were returned by our query as shown below.
const loadingIndicator = app.find('h3');
We then verify that we got something back from .find()
using the following line.
expect(loadingIndicator.getElements().length).toBeTruthy();
And finally, as shown below. we inspect the props of the first element returned by loadingIndicator.getElements()
and verify that the children contains the text Loading...
which is what we show when we are loading data from the server.
expect(loadingIndicator.getElements()[0].props.children).toBe('Loading...');
And so now we have written our first test using Enzyme and Jest.
NOTE: the code to this point is located here: https://github.com/RyanAtViceSoftware/react-testing-best-practices/tree/step-3-our-first-test
Continued in part 3: https://vicesoftware.com/react-testing-part-3/