×
Now that we can run simple specs and take snapshots of our components, let's see how we can test exactly what a component is rendering, as well as certain props that it will have.
To do this, we're going to install an additional library called Enzyme which is made by the wonderful people at AirBnB. Enzyme is going to help us render components in our tests in different ways and provide a jQuery-like API for selecting components and elements.
The following instructions work on projects bootstrapped with create-react-app:
npm install --save-dev enzyme enzyme-adapter-react-16 react-test-renderer enzyme-to-json
In the src
folder, create a file called setupTests.js
:
import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });
Now if we update App.test.js
:
import React from 'react'; import { shallow } from 'enzyme'; import App from './App'; it('renders without crashing', () => { shallow(<App />); });
And then run npm test
, it should work with no errors!
We're going to be using Enzyme to test the content of our React Components. When using Enzyme, there are three different functions we can use:
shallow
Shallow rendering: this is useful to test a component in isolation from every other component, because it does not render children or nested components. Shallow rendering also does not mount a component to the DOM, so there are no lifecycle methods invoked.
In the typical React pattern of smart and dumb components, shallow rendering is especially easy to test ‘dumb’ components (stateless components) in terms of their props and the events that can be simulated.
You should be using shallow
most of the time!
You can read more about it here.
To see this in action, let's continue with our earlier example by including our SimpleComponent
inside of our App
component:
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; import SimpleComponent from './SimpleComponent'; class App extends Component { render() { return ( <div className="App"> <div className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h2>Welcome to React</h2> </div> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> <SimpleComponent first="Matt" last="Lane" favFood="ice cream" /> </div> ); } } export default App;
Next, let's modify the App.test.js
file as follows:
import React from 'react'; import App from '../App'; import toJson from 'enzyme-to-json'; // for snapshotting the component import SimpleComponent from '../SimpleComponent'; import { shallow } from 'enzyme'; describe('<App />', () => { let wrapper; it('renders without crashing', () => { wrapper = shallow(<App />); }); it('matches snapshot', () => { expect(toJson(wrapper)).toMatchSnapshot(); }); it('should render one <SimpleComponent /> component', () => { expect(wrapper.find(SimpleComponent)).toHaveLength(1); expect(wrapper.find('h2')).toHaveLength(1); }); it('should render with a class of App-intro', () => { expect(wrapper.find('.App-intro')).toHaveLength(1); }); });
As you can see, this test is able to detect that the App
component consists of a single SimpleComponent
child, and that there's an element with a class of App-intro
. Note also that with shallow rendering, only one h2
is found: this is the one inside of the App
component, not the one inside of SimpleComponent
.
mount
Full mounting: Also known as full DOM rendering, it allows you to render a part of the DOM tree and it also gives you access to the lifecycle methods of React components (componentWillMount
, componentWillReceiveProps
, etc.). Unlike shallow
, using mount
will render nested components and children.
You can read more about it here. For now, to highlight some simple differences between shallow
and mount
, let's add the following lifecycle hook to our App.js
:
componentDidMount() { this.setState({ name: "My app!" }); }
Next, let's modify our test file by importing mount
and adding a couple more test
blocks:
import React from 'react'; import App from '../App'; import toJson from 'enzyme-to-json'; // for snapshotting the component import SimpleComponent from '../SimpleComponent'; import { shallow, mount } from 'enzyme'; describe('<App />', () => { let wrapper; it('renders without crashing', () => { wrapper = shallow(<App />); }); it('matches snapshot', () => { expect(toJson(wrapper)).toMatchSnapshot(); }); it('should render one <SimpleComponent /> component', () => { expect(wrapper.find(SimpleComponent)).toHaveLength(1); expect(wrapper.find('h2')).toHaveLength(1); }); it('should render with a class of App-intro', () => { expect(wrapper.find('.App-intro')).toHaveLength(1); }); it('should detect no state on shallow rendering', () => { expect(wrapper.state()).toBeNull(); }); it('should detect state and all h2s on mounting', () => { wrapper = mount(<App />); expect(wrapper.state('name')).toBe('My app!'); expect(wrapper.find('h2')).toHaveLength(2); }); });
Note: mount
has the potential to greatly slow down your test runs since it is a full DOM mount. When calling mount
over shallow
, always ask yourself "do I need to do a full mount for this test?" and if so "why". Testing is often a great opportunity to rethink your component structure in terms of stateless vs. stateful, etc.
render
Static rendering: Is sparsely used but when it is the case, serves as means of testing plain HTML. This may be useful if you care about testing the eventual HTML that gets rendered, and not the React Component structure.
You can read more about it here.
If you are wondering when to use each of these methods, you can read a great post here.
As you start writing more tests, it's good to know how much of your application is covered by tests. "Code coverage" refers to the number of lines that have been executed after the tests run.
Let's see what our application looks like so far with coverage!
npm test -- --coverage
When we run this, we will see a nice looking table to show how much coverage our code has, and it is broken down by types of coverage (explanation of different types of coverage here). This is done on a per file basis and gives us a somewhat-useful metric for how thoroughly our code has been tested. It's not realistic that you will always have 100% coverage or that 100% coverage will prevent all bugs, so there are diminishing returns if you try to overly maximize your coverage. It's essential to test your code, but you don't have to go overboard with coverage.
For more information, the test coverage that is built in to Jest is provided by Istanbul.
Complete the Todo testing Exericse
When you're ready, move on to React Router Introduction