- #1: Introduction
- #2: Setting up Next.js Project for testing
- #3: Writing the tests
- #3A: What should I test?
- #3B: Testing App Initial Load
- #3C: User can create a post
- #3D: User can edit a post
- #3E: User can delete a post
- #3F: Snapshot Tests and Conclusion (i.e This article ๐)
- #4: Are my tests enough?
I believe that snapshot tests are extremely important, but where do they find their use case? Any page or component whose data doesn't change often can be easily tested using snapshot tests.
Snapshot tests take a snapshot of your application at a certain state and then fails whenever what ever is in your application doesn't match that saved snapshot state. With snapshot tests, you can easily spot when certain words or grammar have changed in your application, which makes them specially suited for static pages. I test all my static pages using snapshot tests and I believe you should as well.
Let's quickly demonstrate how to use snapshot test. For this let's create a new static page called about.tsx
. In the root of your application run the command below to create a new page called about.
touch pages/about.tsx
Inside the about page, add the following lines of code:
import styles from "../styles/Home.module.scss";
import Link from "next/link";
export default function About() {
return (
<main className={styles.main}>
<h1>About</h1>
<p>
This page is a static page whose content doesn t change often, which makes it perfect for snapshot test
</p>
<p>Always use snapshot tests for static pages, cause that way you can know when details in the page change</p>
<Link href="/">Go Home</Link>
</main>
);
}
Run the following command in the root of your application to create a test file for our about page:
touch __tests__/about.test.tsx
In our about.test.tsx
let's add the following piece of code
import About from "../pages/about";
import { render } from "../utils/tests";
describe("About page", () => {
it("Should render with correct details", () => {
const { asFragment } = render(<About />);
expect(asFragment()).toMatchSnapshot();
});
});
So what are we doing in this test file?
We import the About page which we want to test, after which we import the render
function to use to render our About page (NOTE: Since we are not testing any user interaction in this test, we didn't import setupUserEvent
like in previous user story tests) . Thereafter, we create our describe
and it
block with the necessary descriptions and then write our code to generate a snapshot. Yes, just those two lines ๐
.
When we run our tests a __snapshots__
folder would be created in the same folder as the test file that created the snapshot. This folder would hold a copy of our snapshot.
To test how viable the snapshot test is, let's go back into our about.tsx
page and remove "just a letter"๐
. After doing so, rerun your tests. You would see immediately that the snapshot test fails.
When a codebase gets really large, there are times where a member of the team may mistakenly edit a word, or remove a line, or simply add a letter that was not meant to be there. If this happens in a static file without snapshot tests, it would be extremely hard to detect, since it would not break anything. However, with a snapshot test, one can easily detect that a change has happened in the static file.
Test Descriptions
Over the course of our tests, we always started by writing a description in our it
function call as its first argument, before then writing the actual test as the second argument for our it
function call. However, we haven't really seen the main use of the description. When we run our tests we see something of this nature in our console:
If we simply go to our jest.config
file and add a certain option we would see something more rich. Open your jest config file and addverbose: true
to the configuration. Your jest.config should now look like this.
import nextJest from "next/jest";
const createJestConfig = nextJest({
dir: "./",
});
const customJestConfig = {
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
testEnvironment: "jest-environment-jsdom",
verbose: true,
};
export default createJestConfig(customJestConfig);
We have added our new option directly under the testEnvironment
option. Now, make sure you restart your tests. You should now see all the descriptions you have written in the console.
Really nice, right?๐ . Now if something fails we can easily identify where the failure occurred through the description
Conclusion
We have successfully tested the four different user stories/scenarios that can happen in our application and also tested a static page using a snapshot test. Once these scenarios are up and running one can confidently say that the application is production ready.
Like I said in my previous article, although Unit tests are still extremely important, you can see that in each of our integration tests (i.e the scenarios/user stories) above, we have automatically "indirectly" tested our units. Hence I say again :
It is very possible for all the units of your application to work well independently, yet not work as expected if not tied properly to each other (e.g if the onClick event on a button is not tied to the callback it's expected to be tied to). Maybe you have two buttons with labels "send" and "cancel" and the button that has the text "Send" was mistakenly tied to the callback responsible for "cancel" and not the actual "send" callback.
However it is impossible for your app to work well, and your units not work well (i.e If the "send" button calls the callback responsible for send, then the button is working perfectly). I hope you get that ?
In our next article we would be taking a look at one very important section in testing, which is called "coverage". Coverage answers the question, "How do I know that the tests I have written are enough ?"
See you there !๐