Snapshot testing is useful when you want to notice structural changes quickly, but raw snapshots can become noisy fast. Custom serializers help by stripping away unstable details before the snapshot is written, so the test keeps the signal and drops the churn.
That matters in React because component output often includes values that are correct but not stable: generated class names, inline styles, timestamps, random IDs, or framework-specific wrappers. A serializer gives you one place to normalize that output instead of repeating cleanup logic in every test.
The short version
If your snapshots keep changing for reasons that are not meaningful, add a serializer. If the snapshot should show the important structure but not implementation noise, a serializer is usually the right fix.
The goal is not to hide everything. The goal is to make the snapshot easier to review.
What a serializer does
In Jest, a custom serializer receives a value before it is printed into the snapshot. You can inspect that value, reshape it, or replace unstable fields with stable text.
For React tests, that often means simplifying component trees, masking dynamic IDs, or normalizing generated markup.
const stripTestIds = {
test(value: unknown) {
return typeof value === 'object' && value !== null;
},
print(value: Record<string, unknown>, serialize: (value: unknown) => string) {
const clone = { ...value };
if ('data-testid' in clone) {
clone['data-testid'] = '[removed]';
}
return serialize(clone);
},
};
That example is intentionally small. The important part is the pattern: detect the thing you want to normalize, then return a stable representation.
A common React problem
Here is a simple component that produces output with a generated ID.
type FieldProps = {
label: string;
id: string;
};
export function Field({ label, id }: FieldProps) {
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} data-testid={'field-' + id} />
</div>
);
}
Without a serializer, the snapshot might include the full data-testid value or some other generated detail that changes from test to test. That makes code review slower because every snapshot diff looks like a behavioral change even when it is not.
Registering the serializer
The usual Jest setup is straightforward.
import { stripTestIds } from './serializers/stripTestIds';
expect.addSnapshotSerializer(stripTestIds);
Once that is registered, any snapshot written in that test file can pass through the serializer first.
If you prefer a global setup file, that works too. The point is to keep the normalization close to the snapshot system, not scattered across multiple tests.
Before and after
The difference is easy to see when you compare a raw snapshot with a normalized one.
test('renders the field', () => {
const { container } = render(<Field label="Email" id="email-42" />);
expect(container.firstChild).toMatchSnapshot();
});
With a serializer in place, the stored snapshot can focus on the structure that matters:
<div>
<label htmlFor="email-42">Email</label>
<input
data-testid="[removed]"
id="email-42"
/>
</div>
That is still enough to tell you whether the component renders the right label and input relationship. It is just less fragile.
Good uses for serializers
1 Hide unstable IDs
Normalize generated IDs, test IDs, and random suffixes that are not part of the user-facing behavior.
2 Simplify wrapper noise
Remove framework wrappers or helper markup that makes the snapshot harder to read than it needs to be.
3 Normalize repeated markup
Reduce large repeated structures to the stable shape that the reviewer actually needs to inspect.
A practical rule
Custom serializers are best when the output is still meaningful after the cleanup. If the serializer has to transform almost everything, the snapshot is probably trying to test the wrong thing.
That is the boundary to watch:
When not to use one
If the only reason a snapshot is noisy is that the component is too large, a serializer is not the fix. Split the component or stop snapshotting the whole tree.
If the serializer starts hiding real behavior, remove it. A bad serializer can make a broken UI look stable.
Bottom line
Custom serializers are a small but effective way to keep React snapshots readable. They normalize the unstable parts of the output so the snapshot shows what actually matters.
Use them when the diff is noisy, not when the test is overreaching. That keeps snapshots useful instead of ritualistic.