Search code examples
jestjsbabeljsclass-properties

Variant of toEqual that works with class properties in the same way that it works with class methods


This is the scenario. The first class has a method getName, and the second class has a class property getName. The first class works with toEqual, and the second class does not.

class Person01 {
    constructor(name) { this.name = name; }
    getName() { return this.name; }
}

class Person02 {
    constructor(name) { this.name = name; }
    getName = () => { return this.name; }
}

const testCases = [
    [
        // passes
        new Person01('Alan', 'Kay'),
        new Person01('Alan', 'Kay'),
    ], 
    [
        // fails due to class properties
        new Person02('Alan', 'Kay'),
        new Person02('Alan', 'Kay'),
    ]
];

describe('when one class has the same values that another class has', () =>
    testCases.forEach(pair =>
        it('is considered to be equal to that class', () =>
            expect(pair[0]).toEqual(pair[1]))));

This is the failure message for the second class.

Expected: {"firstName": "Alan", "getName": [Function anonymous], "lastName": "Kay"} 
Received: {"firstName": "Alan", "getName": [Function anonymous], "lastName": "Kay"} 

Our current workaround is to run JSON.parse(JSON.stringify(obj)) on the actual and expected values.

What we're looking for instead is a variant of toEqual that works the same way with class properties as it does with class methods.

Here is our babel.config.js file.

module.exports = function (api) {

  api.env();

  const plugins = [
    "@babel/proposal-class-properties",
  ];

  return {
    plugins,
  };
}

Solution

  • The issue is that function class properties are created per instance...

    ...so toEqual fails since each instance has a different set of function properties.


    One option is to create a custom matcher, but that is tricky since toEqual is doing a lot.

    Another option is to just filter the function properties before using toEqual:

    const filterFunctions = (obj) => 
      Object.keys(obj)
        .filter(k => typeof obj[k] !== 'function')
        .reduce((a, k) => { a[k] = obj[k]; return a; }, {});
    
    describe('when one class has the same values that another class has', () =>
      testCases.forEach(pair =>
          it('is considered to be equal to that class', () =>
              expect(filterFunctions(pair[0])).toEqual(filterFunctions(pair[1])))));  // Success!