SimonVanCasteren SimonVanCasteren - 6 months ago 118
AngularJS Question

Karma/Jasmine: Pretty printing object comparison

I'm currently using the Karma test runner for my Angular project, with the jasmine testing framework. It's working great, but I have one issue: When an object comparison fails, the resulting print into the console is really hard to read, and gets harder the more properties these objects have. Example:

Expected spy spy to have been called with [ { currentCareMoment : { ID : 5, Description : 'Late namiddag (16-20)', StartHour : 16, EndHour : 20 }, previousCareMoment : { ID : 4, Description : 'Namiddag (14-16)', StartHour : 14, EndHour : 16 } } ] but actual calls were [ { currentCareMoment : { ID : 6, Description : 'Avond (20-24)', StartHour : 20, EndHour : 24 }, previousCareMoment : { ID : 5, Description : 'Late namiddag (16-20)', StartHour : 16, EndHour : 20 } } ].


Is there anyway to set up Jasmine (as I think Karma has nothing to do with it) to print objects prettier? Just some line breaks and indentation would already be a huge help. Example:

Expected spy spy to have been called with [ {
currentCareMoment : {
ID : 5,
Description : 'Late namiddag (16-20)',
StartHour : 16,
EndHour : 20
},
previousCareMoment : {
ID : 4,
Description : 'Namiddag (14-16)',
StartHour : 14,
EndHour : 16
}
} ] but actual calls were [ {
currentCareMoment : {
ID : 6,
Description : 'Avond (20-24)',
StartHour : 20,
EndHour : 24
},
previousCareMoment : {
ID : 5,
Description : 'Late namiddag (16-20)',
StartHour : 16,
EndHour : 20
}
} ].

Answer

My answer is based on jasmine 2.0.1.

Method 1 is documented in the jasmine docs. So it is probably recommended.
Method 2 however is much simpler.

Method 1: Using a custom matcher

My initial though was to create a custom matcher as described here. So I copied the toHaveBeenCalledWith matcher from the jasmine source code and modified it so it would pretty print:

var matchers = {
  toBeCalledWith: function (util, customEqualityTesters) {
    return {
      compare: function() {
        var args = Array.prototype.slice.call(arguments, 0),
          actual = args[0],
          expectedArgs = args.slice(1),
          result = { pass: false };

        if (!jasmine.isSpy(actual)) {
          throw new Error('Expected a spy, but got ' + jasmine.JSON.stringify(actual, undefined, 2) + '.');
        }

        if (!actual.calls.any()) {
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but it was never called.';
          };
          return result;
        }

        if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
          result.pass = true;
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but it was.';
          };
        } else {
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but actual calls were ' + JSON.stringify(actual.calls.allArgs(), undefined, 2) + '.';
          };
        }

        return result;
      }
    };
  }
};

The test case would then use our new matcher instead:

describe('Test', function() {

  beforeEach(function() {
    jasmine.addMatchers(matchers);
  });

  it('should print pretty', function() {
    var spy = jasmine.createSpy('spy');
    spy({
      currentCareMoment: {
      ID: 5,
      Description: 'Late namiddag (16-20)',
      StartHour: 16,
      EndHour: 20
    },
    previousCareMoment: {
      ID: 4,
      Description: 'Namiddag (14-16)',
      StartHour: 14,
      EndHour: 16
    }});

    expect(spy).toBeCalledWith({
      currentCareMoment: {
        ID: 6,
        Description: 'Avond (20-24)',
        StartHour: 20,
        EndHour: 24
      },
      previousCareMoment: {
        ID: 5,
        Description: 'Late namiddag (16-20)',
        StartHour: 16,
        EndHour: 20
      }
    });
  });
});

Method 2: Override jasmine.pp

However, during implementing this I noticed jasmine uses the function jasmine.pp for pretty printing. So I figured I could just override this by adding the following on top of my test file:

jasmine.pp = function(obj) {
  return JSON.stringify(obj, undefined, 2);
};