- 1. JavaScript testing #1. Explaining types of tests. Basics of unit testing with Jest
- 2. JavaScript testing #2. Introducing Enzyme and testing React components
- 3. JavaScript testing #3. Testing props, the mount function and snapshot tests.
- 4. JavaScript testing #4. Mocking API calls and simulating React components interactions
- 5. JavaScript testing #5. Testing hooks with react-hooks-testing-library and Redux
- 6. JavaScript testing #6. Introduction to End-to-End testing with Cypress
- 7. JavaScript testing #7. Diving deeper into commands and selectors in Cypress
- 8. JavaScript testing #8. Integrating Cypress with Cucumber and Gherkin
- 9. JavaScript testing #9. Replacing Enzyme with React Testing Library
- 10. JavaScript testing #10. Advanced mocking with Jest and React Testing Library
- 11. JavaScript testing #11. Spying on functions. Pitfalls of not resetting Jest mocks
- 12. JavaScript testing #12. Testing downloaded files and file inputs with Cypress
- 13. JavaScript testing #13. Mocking a REST API with the Mock Service Worker
- 14. JavaScript testing #14. Mocking WebSockets using the mock-socket library
Hello! In the previous part of the tutorial, we’ve covered the very basics of testing React components with Enzyme. Today we will dig a bit deeper and learn how to test props, how (and why) use the mount function and what are the snapshot tests. Let’s go!
Testing props
In the previous article, we’ve tested the outcome of passing some props. But actually, we can test the props directly. Let’s get back to our ToDoList component, but this time let’s use a Task component.
ToDoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import React from 'react'; import Task from "../Task/Task"; const ToDoList = (props) => { return ( <ul> { props.tasks.map(task => <Task key={task.id} id={task.id} name={task.taskName}/> ) } </ul> ) }; export default ToDoList; |
We’re about to test if the ToDoList component renders the Task components and passes them the task names.
ToDoList.test.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import React from 'react'; import { shallow } from 'enzyme'; import ToDoList from './ToDoList'; describe('ToDoList component', () => { describe('when provided with an array of tasks', () => { it('passes them to the Task components', () => { const tasks = [ { id: 0, name: 'Wash the dishes' }, { id: 1, name: 'Make the bed' } ]; const toDoListInstance = shallow( <ToDoList tasks={tasks}/> ); toDoListInstance.find('Task').forEach(taskInstance => { const taskProps = taskInstance.props(); const matchingTask = tasks.find(task => task.id === taskProps.id); expect(taskProps.name).toBe(matchingTask.name); }) }) }); }); |
Thanks to our test we can be sure that the Task components receive the right props from the ToDoList.
We can call the props function thanks to the fact that both toDoListInstance and taskInstance inherit from the ShallowWrapper. In the same manner, you can check the state, or even change it. For a full list of available functions, you can visit the documentation.
But what if we would like to test the actual contents of the <li> tags in the Task component?
1 2 3 4 5 6 |
toDoListInstance.find('Task').forEach(taskInstance => { const taskProps = taskInstance.props(); const matchingTask = tasks.find(task => task.id === taskProps.id); const listItem = taskInstance.first('li'); expect(listItem.text()).toBe(matchingTask.name); }) |
After running the test you will encounter an error! It looks like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
FAIL app/components/ToDoList/ToDoList.test.js ● ToDoList component › when provided with array of tasks › passes them to the Task components expect(received).toBe(expected) // Object.is equality Expected: "Wash the dishes" Received: "<Task />" 23 | const matchingTask = tasks.find(task => task.id === taskProps.id); 24 | const listItem = taskInstance.first('li'); > 25 | expect(listItem.text()).toBe(matchingTask.name); | ^ 26 | }) 27 | }) 28 | }); |
Rendering with the mount function
This fails because we are using a shallow rendering there. With that being the case, the child component will not render at all. That’s why the test above failed and you need to be aware of that limitation of the shallow rendering.
Mount uses a simulation of a DOM implementation and Jest by default uses jsdom. You can change it through the testEnvironment property.
In the previous versions of Enzyme the lifecycle methods weren’t called during the shallow rendering. It changed in the Enzyme 3.0
1 2 3 |
const toDoListInstance = mount( <ToDoList tasks={tasks}/> ); |
Running the code above will cause a whole ToDoList component to render with all its children. As a result, the test that previously was failing will pass now.
Since the mount function renders more and imitates actual DOM, therefore tests will take more time. Using the mount function might mean that you are not unit testing anymore and now you are doing integration tests. It is due to that fact, that with the mount function, you can test how the components work together and not just as separate units.
For a comparison of the terms unit testing and integration testing checkout of the first part of the tutorial.
It might prove to be also useful when testing interactions with DOM or when working with the Higher-Order Components.
To read more about Higher-Order components check out the official guide and a really descriptive post by David Kopal
Snapshot testing
Tests that include using snapshots are quite useful. During such tests, the component is rendered and a snapshot of it is created. It contains a whole structure of the rendered component that should be committed to the repository along with the test itself. When running the snapshot test again, a new snapshot will be compared to the old one. If they will differ, the test will fail. It will help you to make sure that your User Interface does not change unexpectedly.
ToDoList.test.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import React from 'react'; import { shallow } from 'enzyme'; import ToDoList from './ToDoList'; describe('ToDoList component', () => { describe('when provided with an array of tasks', () => { it('should render correctly', () => { const tasks = [ { id: 0, name: 'Wash the dishes' }, { id: 1, name: 'Make the bed' } ]; const toDoListInstance = shallow( <ToDoList tasks={tasks}/> ); expect(toDoListInstance).toMatchSnapshot(); }); }); }); |
Running the code above will create a file called ToDoList.test.js.snap
ToDoList.test.js.snap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ToDoList component when provided with array of tasks should render correctly 1`] = ` <ul> <Task id={0} key="0" name="Wash the dishes" /> <Task id={1} key="1" name="Make the bed" /> </ul> `; |
If you will make any changes to the ToDoList component the test will fail and will present you with an exact difference between snapshots which is very useful.
To update all of your failing snapshots, you can run Jest with the -u flag (alias for --updateSnapshot). To do so, type npm run test -- -u.
You can also run Jest in the watch mode which will allow you to update all of the conflicting snapshots, one by one. In order to do it, run npm run test -- --watchAll and then choose i to update failing snapshots interactively. The official Jest documentation has a quite good animation presenting this process.
Snapshot testing can be a very powerful tool to keep track of changes to your components. It will prevent you from changing a component in an unexpected way, forcing you to look through the changes that you’ve made in order to accept it or fix an issue. Therefore it can be useful as a utility to monitor your code.
Summary
In this article, we’ve covered testing the props of our components and learned the difference between the mount function and the shallow rendering. Aside from that, we’ve covered snapshot testing which can be a very useful tool to track the changes of the way that your components are rendered. In the upcoming articles, we will also cover simulating interactions with our components, so stay tuned!
I think there is a typo in the code in the mount section, should be:
const toDoListInstance = mount(
<ToDoList tasks={tasks}/>
);
You’re right. Thank you! 🙂