Home >Web Front-end >JS Tutorial >How To Remove Dynamic Values From Snapshot With Serializers
Snapshot tests in Jest and Vitest are powerful tools for detecting unexpected changes in your code's output. However, they easily break when dealing with dynamic values like generated IDs or timestamps that change with each test run. While mocking these values is possible, it can lead to unintended side effects.
Consider this user object which could be returned from an API call or database query:
const user = { id: crypto.randomUUID(), name: "John Doe", createdAt: new Date().toISOString() };
Every time you run your tests, the id and createdAt values will be different, causing your snapshots to fail.
Here's how to create a custom serializer that replaces dynamic values with consistent placeholders:
const property = 'id'; const placeholder = '[ID]'; expect.addSnapshotSerializer({ test(val) { return val && typeof val === 'object' && Object.hasOwn(val, property) && val[property] !== placeholder }, serialize(val, config, indentation, depth, refs, printer) { return printer( { ...(val as Record<string, unknown>), [property]: placeholder, }, config, indentation, depth, refs, ); }, });
You can add a custom snapshot serializer with expect.addSnapshotSerializer().
It expects an object with two functions:
test() is used to determine whether this custom serializer should be used. It checks if the value from expect(value) is an object with the property and has not been replaced by the placeholder.
serialize() is only called if test() has returned true. It replaces the property with the placeholder and calls the printer() function to serialize the value into a JSON-like string.
Now, when you run your tests, you will see that id was replaced with the [ID] placeholder:
interface User { id: string; name: string; createdAt: string; } expect.addSnapshotSerializer({ /* ... */ }); test('snapshot', () => { const user: User = { id: '123e4567-e89b-12d3-a456-426614174000', name: 'John Doe', createdAt: '2024-03-20T12:00:00Z', }; expect(user).toMatchInlineSnapshot(` { "id": "[ID]", "name": "John Doe", } `); });
What if we need to handle multiple dynamic properties? Let's create a reusable solution:
export const replaceProperty = ( property: string, placeholder: string, ): SnapshotSerializer => { return { test(val) { return val && typeof val === 'object' && Object.hasOwn(val, property) && val[property] !== placeholder }, serialize(val, config, indentation, depth, refs, printer) { return printer( { ...(val as Record<string, unknown>), [property]: placeholder, }, config, indentation, depth, refs, ); }, }; };
In your tests, you can create multiple serializers for different properties:
expect.addSnapshotSerializer(replaceProperty('id', '[ID]')); expect.addSnapshotSerializer(replaceProperty('createdAt', '[TIMESTAMP]'));
I use these serializers so frequently that I created the npm package snapshot-serializers to make it easier for everyone.
import { replaceProperty, removeProperty } from 'snapshot-serializers'; type User = { id: string; name: string; createdAt: string; password?: string; }; // Type-safe property replacement expect.addSnapshotSerializer( // TypeScript will only allow "id" | "name" | "createdAt" | "password" replaceProperty<User>({ property: 'id', placeholder: '[ID]' }) ); // Remove properties entirely expect.addSnapshotSerializer( removeProperty<User>({ property: 'password' }) ); // This would cause a TypeScript error: expect.addSnapshotSerializer( replaceProperty<User>({ property: 'invalid' // Error: Type '"invalid"' is not assignable... }) );
It provides a type-safe API to replace or remove properties in your snapshots. You can provide a generic type parameter like removeProperty
The above is the detailed content of How To Remove Dynamic Values From Snapshot With Serializers. For more information, please follow other related articles on the PHP Chinese website!