The SDET Playbook

← All questions

How should test code deep-clone fixtures, and how would you test a deep clone utility?

Asked Sep 28, 2026Viewed 1 time

1 Answer

Sign in to answer and to vote.

  • 0
    The SDET PlaybookSep 28, 2026

    In current Node.js and browsers, use the built-in structuredClone. It copies nested objects and arrays, Date, Map, Set, RegExp, typed arrays and circular references. The JSON round trip breaks on all of those: dates become strings, Map and Set become {}, undefined values disappear, and a circular reference throws.

    const baseOrder = { id: 1, createdAt: new Date('2026-01-01'), tags: new Set(['vip']) };
    
    test('applies a discount', () => {
      const order = structuredClone(baseOrder); // this test's own copy
      order.tags.add('discounted');
      // baseOrder is untouched for the next test
    });
    

    Know its limits, because interviewers ask. structuredClone throws on functions and DOM nodes, and it drops class prototypes, so a cloned class instance comes back as a plain object. For fixtures, a factory function that builds a fresh object per test (buildOrder()) is often simpler than cloning at all.

    If you're asked to write one yourself, keep the scope explicit and the cases tested:

    export function deepClone<T>(value: T, seen = new WeakMap<object, unknown>()): T {
      if (typeof value !== 'object' || value === null) return value;
      if (seen.has(value)) return seen.get(value) as T;
    
      if (value instanceof Date) return new Date(value.getTime()) as T;
      if (value instanceof Map) {
        const copy = new Map();
        seen.set(value, copy);
        value.forEach((v, k) => copy.set(deepClone(k, seen), deepClone(v, seen)));
        return copy as T;
      }
      if (value instanceof Set) {
        const copy = new Set();
        seen.set(value, copy);
        value.forEach((v) => copy.add(deepClone(v, seen)));
        return copy as T;
      }
    
      const copy: Record<PropertyKey, unknown> = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
      seen.set(value, copy);
      for (const key of Reflect.ownKeys(value)) {
        copy[key] = deepClone((value as Record<PropertyKey, unknown>)[key], seen);
      }
      return copy as T;
    }
    

    Then test what makes a clone correct:

    it('copies deeply', () => {
      const original = { nested: { n: 1 } };
      const copy = deepClone(original);
      expect(copy).toEqual(original);
      expect(copy.nested).not.toBe(original.nested);
    });
    it('keeps dates, maps and sets as their own types', () => {
      const copy = deepClone({ d: new Date(0), m: new Map([[1, 'a']]), s: new Set([1]) });
      expect(copy.d).toBeInstanceOf(Date);
      expect(copy.m.get(1)).toBe('a');
      expect(copy.s.has(1)).toBe(true);
    });
    it('handles circular references', () => {
      const a: { self?: unknown } = {};
      a.self = a;
      const copy = deepClone(a);
      expect(copy.self).toBe(copy);
    });
    

    State what it deliberately doesn't handle (getters and setters, non-enumerable properties, class private fields) instead of claiming it handles everything.

    Sources: MDN: structuredClone(), MDN: the structured clone algorithm