Back to Repositories

Testing Advanced Serialization Implementation in Parcel Bundler

This test suite validates the serializer functionality in Parcel, focusing on object serialization and deserialization with complex data structures and custom class handling. It ensures reliable data transformation and state preservation across different scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of serialization scenarios including:

  • Basic object serialization and deserialization
  • Complex object references and cyclic dependencies
  • Built-in JavaScript collections (Map, Set)
  • Custom class serialization with inheritance
  • Raw value handling and optimization cases

Implementation Analysis

The testing approach utilizes Jest’s describe/it blocks with systematic validation of serialization behaviors. Implementation includes flow type annotations and leverages Node.js Buffer for binary data handling. The tests demonstrate both synchronous operations and class-based serialization patterns.

Technical Details

Testing tools and setup:

  • Jest test framework
  • Flow type checking
  • Sinon for mocking and spying
  • Node.js assert module
  • Custom serializer/deserializer implementation

Best Practices Demonstrated

The test suite exemplifies strong testing practices including:

  • Isolated test cases with clear assertions
  • Proper cleanup with beforeEach/afterEach hooks
  • Edge case coverage for cyclic references
  • Verification of instance preservation
  • Type safety checks

parcel-bundler/parcel

packages/core/core/test/serializer.test.js

            
// @flow
import {
  serialize,
  deserialize,
  registerSerializableClass,
  unregisterSerializableClass,
} from '../src/serializer';
import assert from 'assert';
import sinon from 'sinon';

describe('serializer', () => {
  it('should serialize a basic object', () => {
    let serialized = serialize({foo: 2, bar: 3});
    assert(Buffer.isBuffer(serialized));
    let deserialized = deserialize(serialized);
    assert.equal(typeof deserialized, 'object');
    assert.deepEqual(deserialized, {foo: 2, bar: 3});
  });

  it('should serialize an object with multiple references', () => {
    let a = {foo: 2};
    let b = {bar: a, baz: a};
    let res = deserialize(serialize(b));
    assert.deepEqual(res, b);
    assert.equal(res.bar, res.baz);
  });

  it('should serialize a cyclic object', () => {
    let a = {foo: 2, bar: {}};
    a.bar = a;
    let res = deserialize(serialize(a));
    assert.deepEqual(res, a);
    assert.equal(res.bar, res);
    assert.equal(a.bar, a);
  });

  it('should serialize a Map', () => {
    let a = new Map([[2, 3]]);
    let res = deserialize(serialize(a));
    assert(res instanceof Map);
    assert.equal(res.get(2), 3);
  });

  it('should serialize a Set', () => {
    let a = new Set([2, 3]);
    let res = deserialize(serialize(a));
    assert(res instanceof Set);
    assert(res.has(2));
    assert(res.has(3));
  });

  it('should serialize a class', () => {
    class Test {
      x: number;
      constructor(x: number) {
        this.x = x;
      }
    }

    registerSerializableClass('Test', Test);

    let x = new Test(2);
    let res = deserialize(serialize(x));
    assert(res instanceof Test);
    assert.equal(res.x, x.x);

    unregisterSerializableClass('Test', Test);
  });

  it('should serialize a class with a custom serialize method', () => {
    class Test {
      x: number;
      constructor(x: number) {
        this.x = x;
      }

      serialize() {
        return {
          x: this.x,
          serialized: true,
        };
      }
    }

    registerSerializableClass('Test', Test);

    let x = new Test(2);
    let res = deserialize(serialize(x));
    assert(res instanceof Test);
    assert.equal(res.x, x.x);
    assert.equal(res.serialized, true);

    unregisterSerializableClass('Test', Test);
  });

  it('should serialize a class with a custom deserialize method', () => {
    class Test {
      x: number;
      constructor(x: number) {
        this.x = x;
      }

      static deserialize(x: any) {
        return {
          deserialized: true,
          value: x,
        };
      }
    }

    registerSerializableClass('Test', Test);

    let x = new Test(2);
    let res = deserialize(serialize(x));
    assert(!(res instanceof Test));
    assert.equal(res.value.x, x.x);
    assert.equal(res.deserialized, true);

    unregisterSerializableClass('Test', Test);
  });

  it('should serialize a class recursively', () => {
    class Foo {
      x: number;
      constructor(x: number) {
        this.x = x;
      }
    }

    class Bar {
      foo: Foo;
      constructor(foo: Foo) {
        this.foo = foo;
      }
    }

    registerSerializableClass('Foo', Foo);
    registerSerializableClass('Bar', Bar);

    let x = new Bar(new Foo(2));
    let res = deserialize(serialize(x));
    assert(res instanceof Bar);
    assert(res.foo instanceof Foo);
    assert.equal(res.foo.x, 2);

    unregisterSerializableClass('Foo', Foo);
    unregisterSerializableClass('Bar', Bar);
  });

  it('should serialize a cyclic class', () => {
    class Foo {
      x: ?Foo;
      constructor(x: ?Foo) {
        this.x = x;
      }
    }

    registerSerializableClass('Foo', Foo);

    let x = new Foo();
    x.x = x;

    let res = deserialize(serialize(x));
    assert(res instanceof Foo);
    assert(res.x instanceof Foo);
    assert.equal(res.x, res);

    assert.equal(x.x, x);

    unregisterSerializableClass('Foo', Foo);
  });

  it('should copy on write', () => {
    class Foo {
      x: number;
      constructor(x: number) {
        this.x = x;
      }
    }

    registerSerializableClass('Foo', Foo);

    let x = {y: {foo: new Foo(2)}};

    let res = deserialize(serialize(x));
    assert(res.y.foo instanceof Foo);
    assert(x.y.foo instanceof Foo);

    unregisterSerializableClass('Foo', Foo);
  });

  it('should serialize a cyclic class and copy on write', () => {
    class Foo {
      x: ?Foo;
      constructor(x: ?Foo) {
        this.x = x;
      }
    }

    registerSerializableClass('Foo', Foo);

    let x = new Foo();
    x.x = x;
    let y = {x: {y: x}};

    let res = deserialize(serialize(y));
    assert(res.x.y instanceof Foo);
    assert(res.x.y.x instanceof Foo);
    assert(y.x.y instanceof Foo);
    assert(y.x.y.x instanceof Foo);
    assert.equal(res.x.y.x, res.x.y);

    assert.equal(x.x, x);

    unregisterSerializableClass('Foo', Foo);
  });

  it('should serialize a class inside a Map', () => {
    class Test {
      x: number;
      constructor(x: number) {
        this.x = x;
      }
    }

    registerSerializableClass('Test', Test);

    let x = new Map([[2, new Test(2)]]);
    let res = deserialize(serialize(x));
    assert(res instanceof Map);
    assert(res.get(2) instanceof Test);

    unregisterSerializableClass('Test', Test);
  });

  it('should serialize a class inside a Set', () => {
    class Test {
      x: number;
      constructor(x: number) {
        this.x = x;
      }
    }

    registerSerializableClass('Test', Test);

    let x = new Set([new Test(2)]);
    let res = deserialize(serialize(x));
    assert(res instanceof Set);
    assert(res.values().next().value instanceof Test);

    unregisterSerializableClass('Test', Test);
  });

  describe('raw values', () => {
    class Outer {
      inner: Inner;

      constructor(inner) {
        this.inner = inner;
      }

      serialize() {
        return {
          $$raw: true,
          inner: this.inner,
        };
      }
    }

    class Inner {
      x: number;

      constructor(x) {
        this.x = x;
      }

      static deserialize = sinon.spy();
    }

    beforeEach(() => {
      registerSerializableClass('Outer', Outer);
      registerSerializableClass('Inner', Inner);
    });

    afterEach(() => {
      unregisterSerializableClass('Outer', Outer);
      unregisterSerializableClass('Inner', Inner);
    });

    it('should not recursively serialize raw values', () => {
      let res = deserialize(serialize(new Outer(new Inner(42))));
      assert(res instanceof Outer);
      assert(!(res.inner instanceof Inner));
      assert.equal(res.inner.x, 42);
    });

    it('should not recursively deserialize raw values', () => {
      deserialize(serialize(new Outer(new Inner(42))));
      assert(Inner.deserialize.notCalled);
    });
  });
});