Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project last week where I had to build a "User Permissions Report." The function takes a raw user object from the database and transforms it into a deeply nested JSON structure that the frontend uses to render a complex permissions grid. It’s a lot of mapping, filtering, and formatting.

The Tedium of Deep Equality

Initially, I did what we've done in previous lessons: I wrote a handful of assertions. I checked the username, the primary role, and maybe one or two nested flags. It looked like this:

test('formats user permissions correctly', () => {
  const rawUser = { id: 1, roles: ['admin', 'editor'], settings: { theme: 'dark' } };
  const result = formatPermissionsReport(rawUser);

  expect(result.user.displayName).toBe('Admin User');
  expect(result.permissions.canEdit).toBe(true);
  expect(result.metadata.generatedAt).toBeDefined();
});

But here's the problem: the actual output object has about 40 different fields. I realized that if I wanted to be truly sure I hadn't broken anything, I'd have to write 40 lines of expect().toBe(). That's not just boring; it's a maintenance nightmare. If I decide to rename a field from canEdit to hasEditAccess, I have to go hunting through my tests to update every single assertion.

Letting the Tool Do the Recording

I wondered if there was a way to just say, "I don't know exactly what the whole object looks like right now, but whatever it is, it should stay exactly like this in the future." That's where I stumbled back into snapshot testing using Jest.

I swapped out all those individual assertions for a single line:

test('formats user permissions correctly', () => {
  const rawUser = { id: 1, roles: ['admin', 'editor'], settings: { theme: 'dark' } };
  const result = formatPermissionsReport(rawUser);
  
  expect(result).toMatchSnapshot();
});

When I ran this for the first time, the test actually failed. But it didn't fail because the code was wrong; it failed because there was no "snapshot" to compare it to. Jest basically said, "I've never seen this object before, so I can't tell you if it changed." But in the process of failing, it automatically created a __snapshots__ folder in my directory and saved the current output of result into a file.

Now, the test passes. The snapshot file acts as the "source of truth." If I accidentally delete a line of logic in my formatter tomorrow, Jest will compare the new output to that saved file and show me a beautiful diff of exactly which field disappeared.

Dealing with the "Oops" and the "Intended"

Now, here is where it gets interesting. I decided to add a lastLogin field to the report. I ran the test, and—surprise—it failed. Jest showed me that the object now had an extra field that wasn't in the snapshot.

In a traditional test, I'd be adding another expect().toBe(). But here, I had to make a choice: was this failure a bug (an "Oops") or a feature (an "Intended" change)? Since I intended to add the field, I didn't change the code. Instead, I ran the test command with the update flag:

npm test -- -u

This tells Jest: "The current output is the new correct version. Overwrite the old snapshot with this one." It's a much faster workflow for evolving data structures. Just be careful—if you blindly hit -u without checking the diff, you might accidentally "baseline" a bug into your tests, effectively telling Jest that the broken behavior is now the expected behavior.

One last thing I noticed: the generatedAt timestamp was causing the test to fail every single time I ran it because the time always changed. Snapshots hate non-deterministic data. To fix this, I had to mock the system time or use "asymmetric matchers" to tell Jest, "I expect a string here, but I don't care exactly what the string is."

expect(result).toMatchSnapshot({
  metadata: {
    generatedAt: expect.any(String)
  }
});



📋 Practical Task

Refactoring the API Response Validator

You have a function called processApiResponse that cleans up a messy API response into a structured object. Currently, the test suite uses 12 different expect().toBe() calls to verify the output, making it fragile and hard to read.

Your goal:

  • Replace the long list of individual assertions with a single toMatchSnapshot() call.
  • Intentionally introduce a bug by changing a key name in the processApiResponse function (e.g., change userName to user_name).
  • Run the test and observe the diff in the console to see how snapshot testing identifies the exact change.
  • Fix the bug and use the update flag (-u) to refresh the snapshot after adding a new field called apiVersion: "v1" to the processed object.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.