Hackernoon logoSnapshot testing React Components with Jest by@lukepierotti

Snapshot testing React Components with Jest

Author profile picture

@lukepierottiLuke Pierotti

After my previous post about using Jest with Enzyme to test React components, I wanted to go a little more in depth with what Jest can do all by itself. By using built in functionality its easy to keep track of your components and what they are rendering, or if they render at all. So lets start snapshot testing!

What is Snapshot testing?

A snapshot test is essentially what the name implies. Jest takes the component that it is testing, renders it, and then takes a snapshot of what the component should look like. When continuing to work on your project, every time you run your test suite, Jest will do the process over again and compare the old snapshot to a new one. If the snapshots do not match, then they an error message shows in the terminal and highlights the parts that do not match.

Snapshot testing while very useful is not considered TDD. Snapshot testing requires a component to be written already so it can take a picture of it. Snapshot testing also relies on the fact that your component renders correctly already. TDD requires that you write tests for your component that originally fail, and as you code the component, it passes the tests. So while snapshot testing is not TDD, it can still be a useful tool for keeping track of your components and making sure they do not change unexpectedly.

Setting up Snapshot Testing

So to begin testing, we need to import a couple of things at the top of your test file and download a package. We will be using renderer which is supplied by the React Test Renderer package. To install it, just run this code,

npm install react-test-renderer

After that is installed we need to import it into our file with React and our component that we are testing. Now that we have all of these imported we can begin writing the snapshot test.

// app/src/components/__tests__/Login-test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Login from '../Login';

I’m going to use a Login component that I used in my previous post. My component looks like this

// app/src/components/Login.js
import React from 'react'
class Login extends React.Component {
constructor() {
super()
this.state = {
username: '',
password: ''
}
}
handleInputChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
return (
<form className='login'>
<label>Username</label>
<input id='email' onChange={this.handleInputChange} name='email' type='text' />
<label>Password</label>
<input id='password' onChange={this.handleInputChange} name='password' type='password' />
<button>Submit</button>
</form>
)
}
}
export default Login

And my test file looks like this already,

// app/src/components/__tests__/Login-test.js
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Login from '../Login'
import renderer from 'react-test-renderer';
describe('Login Component', () => {
  it('should render without throwing an error', () => {
expect(shallow(<Login />).exists(<form className='login'></form>)).toBe(true)
})
  it('renders a email input', () => {
expect(shallow(<Login />).find('#email').length).toEqual(1)
})
  it('renders a password input', () => {
expect(shallow(<Login />).find('#password').length).toEqual(1)
})

describe('Email input', () => {

it('should respond to change event and change the state of the Login Component', () => {
const wrapper = shallow(<Login />)
wrapper.find('#email').simulate('change', {target: {name: 'email', value: 'blah@gmail.com'}})
expect(wrapper.state('email')).toEqual('blah@gmail.com')
})
})

describe('Password input', () => {

it('should respond to change event and change the state of the Login Component', () => {
const wrapper = shallow(<Login />)
wrapper.find('#password').simulate('change', {target: {name: 'password', value: 'cats'}})
expect(wrapper.state('password')).toEqual('cats')
})
})
})

These tests are not important to go into, but I wanted to supply them because they appear later in the terminal. So if you look at the documentation for Jest here, you can see sample code for how create a snapshot. What we do is we call the create function on the renderer object, and pass our component in as the argument. The new code I’ll add to my test file will be this,

// app/src/components/__tests__/Login-test.js
it('renders correctly', () => {
const tree = renderer.create(
<Login />
).toJSON();
expect(tree).toMatchSnapshot();
});

When this is run, the renderer renders the component. Then in the next line we check to see if our rendered component matches the previous snapshot. When we run npm test, Jest creates a new folder within our __tests__ folder called __snapshots__ and within that folder a file for each snapshot test. Because my test file was named Login-test.js the snapshot that is automatically created is the Login-test.js.snap. The file looks like this,

// app/src/components/__tests__/__snapshots__/Login-test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<form
className="login"
>
<label>
Username
</label>
<input
id="email"
name="email"
onChange={[Function]}
type="text"
/>
<label>
Password
</label>
<input
id="password"
name="password"
onChange={[Function]}
type="password"
/>
<button>
Submit
</button>
</form>
`;

This is just a representation of what our component looks like in when it is rendered. The terminal will have this message, showing that we have one new snapshot that was created. It looks like this,

So lets say we accidentally deleted our submit button. As soon as I change the component and save the file, I get an error in the console letting me know that the component doesn’t look right. It highlights the code that I am missing in green, and if I were to add some other code that was not there before it would highlight it in red.

Our component is missing the button shown in green

Looking at the error message Jest gives us a little help. It tells us to check our code changes and if our component is how we want it to render, we can type u to update the snapshot. When I type u the tests run again and I get a passing snapshot test.

Snapshot is updated

But this isn’t right, because I need that button to submit my form. When I add it back you can see the button now shows up in red, meaning that it is code that was added to the component.

Our snapshot does not contain the button so it shows up in red

If I update it one more time we have the component back the way I wanted with all tests passing.

Everything is back to normal and our snapshot is correct once again.

Summary

So as you can see, snapshot testing is a very powerful tool to keep track of any changes to your components. If something unexpectedly changes, Jest will alert you. You then need to simply type u to update your snapshot, or check out your code and fix the problem area. As your codebase for your application grows, this can be the perfect way to monitor all the components and catch bugs. Like I mentioned earlier this does not replace testing or TDD ,but can compliment it and be a useful tool to monitor your code. With this being so easy to do, there is no reason not to implement snapshot testing!

Sources:

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.