How I Stopped My Jest Snapshots From Churning

A serializer that strips dates, ids, and request hashes out of snapshot JSON so a fresh git clone does not nuke the snapshot diff. Drop into `snapshotSerializers` and forget.

JavaScript
Frontend
3 snippets
testing
unit-testing
utility
code-template
marcusreddy

By @marcusreddy

December 14, 2025

·

Updated May 20, 2026

368 views

12

4.2 (11)

// Walk a JS value, replace anything that looks volatile with a stable token.
// Stage 1: pure function, no Jest dependency. You can run this on any object.

const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const HEX_HASH = /^[0-9a-f]{32,}$/i;
const REDACT_KEYS = new Set(['createdAt', 'updatedAt', 'requestId', 'traceId', 'sessionId']);

function redact(value, parentKey = '') {
    if (REDACT_KEYS.has(parentKey)) return `<${parentKey}>`;
    if (typeof value === 'string') {
        if (ISO_DATE.test(value)) return '<ISO_DATE>';
        if (UUID.test(value)) return '<UUID>';
        if (HEX_HASH.test(value)) return '<HEX>';
        return value;
    }
    if (Array.isArray(value)) return value.map((v) => redact(v));
    if (value && typeof value === 'object') {
        const out = {};
        for (const [k, v] of Object.entries(value)) out[k] = redact(v, k);
        return out;
    }
    return value;
}

const sample = {
    id: 'a3f1b8d2-4c7e-4f3a-9b81-1234567890ab',
    user: 'alice',
    createdAt: '2024-08-22T14:03:11.521Z',
    requestId: 'req_8f0e',
    payload: {
        traceId: '7c1ad9e0',
        contentHash: '7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730',
    },
};

console.log(JSON.stringify(redact(sample), null, 2));

The win is in two layers: a key-name denylist for fields you know are volatile (createdAt, requestId, traceId) and a value-shape sweep for things that look like dates, uuids, or hashes regardless of where they appear. I have learned to keep them separate because the shape sweep catches surprises: an upstream API adds a metadata.runId: '7c1ad9...' and your snapshot does not break. The output uses angle-bracket tokens (<ISO_DATE>) rather than dropping the field, so a missing field still fails the snapshot but a churning timestamp does not.