At Neon we develop a number applications – from small scale, to large scale.
React is our tool of choice for many of these builds, due to its blisteringly fast performance, excellent developer tools, React Native for mobile experiences, and the clean abstraction that it encourages. This abstraction allows us to think about our application in smaller, more manageable chunks, which in turn, makes it easier to reuse and more importantly: test.
Testing can be a developer’s best friend and worst enemy; on one hand it can provide a clear set of instructions about what needs to be coded and the confidence that what has been built will actually work correctly, but on the other hand, tests take time to create and must be maintained when code is refactored.
The real strength in testing comes whilst working as part of a team. When Developer B comes to make changes to Developer A’s component further down the line, they are able to quickly see what is currently expected from the component and if any of their new amends have affected the expected behaviour in any way.
We use Jest as a test runner for our applications. Jest was built by Facebook and, in its own words:
Enzyme is a JavaScript Testing utility built by Airbnb that makes it easier to assert and manipulate your React Components’ output.
Enzyme’s API is meant to be intuitive and flexible by mimicking jQuery’s API for DOM manipulation and traversal, which makes testing-specific nested components a breeze as we will cover further in this post.
We use Styled Components to apply CSS styles to our individual components for a number of reasons, including the ability to use SCSS nesting, apply conditional styling rules based on passed in props, and the ability to use the same CSS for React Native applications.
Jest, although packaged with Create React App by default is not specifically designed to be used with React and so can be used to test any javascript code with minimum setup.
Firstly, we use yarn instead of npm, as it is currently faster and has many other improvements.
create-react-app my-app
This will install Jest and the required dependencies that you need to run a basic test.
yarn add enzyme jest-enzyme react-test-renderer jest-styled-components --dev
yarn add styled-components
We are now setup to create a styled component and tests.
A good rule of thumb for unit testing in general is to write your tests first; this gives you a clear guide of what needs to be achieved and you get that warm, fuzzy feeling of your tests passing one by one as you complete each step of your component. However, fuzzy feeling aside, one of the main benefits for Jest is Snapshot testing, which encourages you to write your component first and test the difference between the outputs after.
The basic idea is to create a test that automatically generates a JSON file of your component’s rendered output. Each time a new test is run, Jest will check the difference between the stored ‘snapshot’ of your component and the new JSON output and flag any differences. This greatly speeds up writing tests as you do not need to test that each title should equal the same title as before.
Let’s start with a basic styled component that loops through passed in props and renders a UL element:
import React from 'react'
import { array } from 'prop-types'
// Import the styles from a separate file and
// prepend component name with 'Styled' for consistency
import StyledList from './List.styles'
class List extends React.Component {
render() {
return (
<StyledList>
<ul>
{this.props.items.map((item, key) =>
<li key={key}>
{item.title}
</li>
)}
</ul>
</StyledList>
)
}
}
List.propTypes = {
items: array
}
export default List
import styled from 'styled-components'
const List = styled.div`
position: relative;
background-color: #ffffff;
`
export default List
import styled from 'styled-components'
const List = styled.div`
position: relative;
background-color: #ffffff;
`
export default List
_tests_/List.test.js
import React from 'react'
import renderer from 'react-test-renderer'
import { shallow, mount } from 'enzyme'
import 'jest-enzyme'
import 'jest-styled-components'
// Import component to be tested
import List from '../List'
describe('Component: List', () => {
const items =
{ title: 'List Item 1' },
{ title: 'List Item 2' },
{ title: 'List Item 3' },
{ title: 'List Item 4' }
]
// Snapshot test
it('should render', () => {
const component = <List items={items} />
const tree = renderer.create(component).toJSON()
expect(tree).toMatchSnapshot()
})
})
yarn test
or for auto refresh every time you change a file:
yarn test -- --watch
An extra --coverage
attribute can be passed that will generate a code coverage report in the console, highlighting which lines are still to be tested. A HTML report is also created by default in the coverage/
folder:
Snapshot testing is excellent to test the overall output of the component, but you might be thinking, “what about the nested items inside?” This is where mount from the Enzyme library really shines.
To test that, the List component renders what we expect based on the props we pass in, the mount and find functions allow us to traverse down the DOM until we find the list items and it returns a count that we can test against.
it('should render 4 list items based on props.items', () => {
const component = mount(<List items={items} />)
expect(component.find('li')).toHaveLength(4)
})
Here is the updated List component with a button and new onClick
prop that is called when a user clicks the button:
import React from 'react'
import { array, func } from 'prop-types'
// Import the styles from a separate file and
// prepend component name with 'Styled' for consistency
import StyledList from './List.styles'
class List extends React.Component {
render() {
return (
<StyledList>
<ul>
{this.props.items.map((item, key) =>
<li key={key}>
{item.title}
</li>
)}
</ul>
<button type="button" onClick={this.props.onClick}>
Do Something
</button>
</StyledList>
)
}
}
List.propTypes = {
items: array,
onClick: func
}
export default List
This can be tested using the Enzyme simulate
function to simulate user events.
it('should call props.onChange when button is clicked', () => {
const mockFunction = jest.fn() // In-built Jest function mocker
const component = mount(<List items={items} onClick={mockFunction} />)
// Test before event
expect(mockFunction).not.toHaveBeenCalled()
// simulate the click event
component.find('button').simulate('click')
// Test after event
expect(mockFunction).toHaveBeenCalled()
// Can also test inner text of button
expect(component.find('button').text()).toContain('Do Something')
})
We have covered how to test the expected functionality of components by using the jest-styled-components
library we installed earlier, we can now test the CSS styling of components too.
A simple example would be to test that the background of our component is black (#000000).
More Jest Styled Component examples, including media query testing, can be found here.
it('should render List with the correct styles', () => {
const component = mount(<List items={items} onClick={jest.fn()} />)
expect(component).toHaveStyleRule('background-color', '#000000')
})
More Jest Styled Component examples, including media query testing can be found here.
Use Enzyme’s debug method to print renderer’s output:
const wrapper = mount(/*~*/)
console.log(wrapper.debug())
The next instalment of this Jest testing guide will include some slightly more advanced testing techniques for React Components including:
And more…
Using Jest testing for our React applications has given us confidence in the code that we write and has forced us to think about our projects in a more modular way. We have found this has helped to validate edge cases with ease, and flatten out bugs that we might have missed otherwise.
Do you have any questions about React applications, Jest testing or Enzyme? Tweet us @createdbyneon and let our developers know what you’d like to see in part 2 of this guide!
Want to be at the top of the search ranks? How about a website that’ll give your audience a great experience? Or maybe you’re looking for a campaign that’ll drive more leads? Get in touch to find out how we can help.