- 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
- 15. JavaScript testing #15. Interpreting the code coverage metric
- 16. JavaScript testing #16. Snapshot testing with React, Jest, and Vitest
- 17. JavaScript testing #17. Introduction to End-to-End testing with Playwright
- 18. JavaScript testing #18. E2E Playwright tests for uploading and downloading files
Unit testing is handy for testing how our React components behave when the user interacts with them. However, this might not be enough. Besides testing the logic behind a particular component, we should also test if our user interface changes unexpectedly. We can use snapshot testing to detect unintended modifications to how our components render.
Introduction to snapshot testing
Let’s create a straightforward React component so that we can test it. Its job is to render a user’s name and email.
UserProfile.tsx
In our test, we need to render our component and use the expect().toMatchSnapshot() function. There are a few ways to do that.
The official Jest docs mention the react-test-renderer library that renders our component and provides its representation in JSON.
UserProfile.test.tsx
However, there is a high chance that you already use the React Testing Library, the most popular solution for testing React components. It provides the asFragment function, which returns a document fragment with the current state of our component’s rendered output.
UserProfile.test.tsx
How the snapshot is created
The first time we run our test, the toMatchSnapshot() function creates a snapshot file in the __snapshots__ directory.
UserProfile.test.tsx.snap
If we’re using Jest instead of Vitest, the first line in the file will mention Jest.
The snapshot file contains the output of our React component at the time we called the asFragment() function. We should commit the snapshot file to our repository and treat it as an integral part of our test.
Updating snapshots
Let’s modify our component slightly.
UserProfile.tsx
Above, we changed one of the paragraphs to <h2>. When we re-run our tests, the toMatchSnapshot() function compares the modified component with the snapshot we saved previously. This will fail because the rendered component does not match the snapshot.
When this happens, our testing framework will ask us if we want to update the snapshot. If we do, we will have to commit it to the repository again. We will have to do that every time we make changes to our code that affect how the component is rendered.
Avoiding non-deterministic tests
Let’s create a component that displays the current date.
CurrentDate.tsx
First, let’s write a simple snapshot test for it using the knowledge we already have.
CurrentDate.test.tsx
When we look at the snapshot file created, we can see that it contains the current date.
CurrentDate.test.tsx.snap
Unfortunately, this makes our test non-deterministic. If we run it tomorrow, it will fail. To deal with that, we should mock the current date. Fortunately, both Vitest and Jest support it.
If you want to know more about mocking, check out the following articles:
JavaScript testing #10. Advanced mocking with Jest and React Testing Library
JavaScript testing #13. Mocking a REST API with the Mock Service Worker
CurrentDate.test.tsx
When using Jest, use jest.useFakeTimers().setSystemTime() and jest.useRealTimers() instead.
Thanks to the above approach, our test will always use the same date, no matter when we run it.
Loading data asynchronously
Let’s create a simple component fetching a list of posts from a REST API.
Posts.tsx
The usePosts custom hook loads the data asynchronously through the useEffect hook.
usePosts.tsx
It uses the fetchPosts function under the hood to fetch the data.
fetchPosts.tsx
Thanks to that, we can easily mock the fetchPosts function.
First, let’s create a snapshot when the posts are still loading.
Posts.test.tsx
Second, let’s create a second snapshot when the posts are already loaded. In this case, calling the asFragment() function at the right moment is crucial. We need to wait for the posts to be loaded.
Posts.test.tsx
Running this test suite creates a snapshot file that contains two snapshots.
Posts.test.tsx.snap
Summary
In this article, we’ve discussed snapshot tests and implemented them in simple and more advanced scenarios that involved mocking and asynchronous data loading.
Snapshot testing can help ensure that the user interface does not change unexpectedly. It can also come in handy in situations where we have complex components and tracking the changes manually would be especially difficult. Therefore, incorporating snapshot testing can make our codebase more reliable and maintainable by making it easier to catch any unintended changes early in the development process.