Jasmine equality check for complex objects

Jasmine’s default equality matcher is really good (it’s an adapted version of underscore’s isEqual) but when you work with complex objects, you may want to have more control on what can be equal to what.

Let’s see an example:

var Person = (name, surname) {
  this.name = name;
  this.surname = surname;
};

Person.prototype.render = function() {
  if (!this._cached) {
    this._cached = this.name + " " + this.surname;
  };
};

describe('Person', function() {
  var p1, p2;
  beforeEach(function() {
    p1 = new Person('Indiana', 'Jones');
    p2 = new Person('Indiana', 'Jones');
  });

  it('they are equal', function() {
    p1.render();
    expect(p1).toEqual(p2);
  });
});

In this case the spec will fail because even if the two objects are representing the same person, they have different properties (p1 has a _cached property that has been set during the invocation of render).

My solution in this case is to create the following custom equality matcher for jasmine:

beforeEach(function() {
  jasmine.addCustomEqualityTester(function(actual, expected) {
    var toPrimitive = function(o) {
      if (o == null) { return o; }
      if (o instanceof Array) {
        result = [];
        o.forEach(function(i) { result.push(toPrimitive(i)); });
        return result;
      }
      return o.toPrimitive ? o.toPrimitive() : o;
    },
    actualPrimitive = toPrimitive(actual),
    expectedPrimitive = toPrimitive(expected);
    return jasmine.matchersUtil.equals(actualPrimitive, expectedPrimitive);
  });
});

and to change the example code as the following:

var Person = (name, surname) {
  this.name = name;
  this.surname = surname;
};

Person.prototype.render = function() {
  if (!this._cached) {
    this._cached = this.name + " " + this.surname;
  };
};    

Person.prototype.toPrimitive = function() {
  return { name: this.name, surname: this.surname };
};

describe('Person', function() {
  var p1, p2;
  beforeEach(function() {
    p1 = new Person('Indiana', 'Jones');
    p2 = new Person('Indiana', 'Jones');
  });

  it('they are equal', function() {
    p1.render();
    expect(p1).toEqual(p2);
  });

  it('a person is equal to a plain object', function() {
    expect(p1).toEqual({name: 'Indiana', surname: 'Jones'});
  });
});

Spec passing.

When the object contains the toPrimitive function, the equality matcher will rely on this one to obtain a more simple object to use to check the equality.

Leave a Reply

wpDiscuz