21

Currently, I have a function that sometimes return an object with some functions inside. When using expect(...).toEqual({...}) it doesn't seem to match those complex objects. Objects having functions or the File class (from input type file), it just can't. How to overcome this?

pocesar
  • 6,860
  • 6
  • 56
  • 88

6 Answers6

18

Try the Underscore _.isEqual() function:

expect(_.isEqual(obj1, obj2)).toEqual(true);

If that works, you could create a custom matcher:

this.addMatchers({
    toDeepEqual: function(expected) {
        return _.isEqual(this.actual, expected);
    };
});

You can then write specs like the following:

expect(some_obj).toDeepEqual(expected_obj);
Elletlar
  • 3,136
  • 7
  • 32
  • 38
Vlad Magdalin
  • 1,692
  • 14
  • 17
  • gives me the same result from `toEqual` => `Error: Expected { exception : Function, data : Function, proxy : Function, remote : Function, append_args : Function, set_args : Function, get_args : Function, remove : Function, make : Function, unmake : Function } to deep equal { exception : Function, data : Function, proxy : Function, remote : Function, append_args : Function, set_args : Function, get_args : Function, remove : Function, make : Function, unmake : Function }.` – pocesar Jan 26 '13 at 22:10
  • Are the objects you are comparing composed only of functions? – Vlad Magdalin Jan 26 '13 at 22:15
  • in that particular case, yes, but sometimes I have classes along with plain objects and native wrappers, like the `File` and `FileList` classes – pocesar Jan 27 '13 at 01:37
  • awesome that (JSON.stringify) works... I'll accept your answer after you accept the modification :) – pocesar Jan 27 '13 at 01:41
  • actually, it didn't work, after checking the output of that function, it's showing as `{}`, which is clearly wrong, it seems it can't convert functions to string (like using `(function(){}).toString()`) – pocesar Jan 27 '13 at 01:49
  • Is it important for you that the functions match? If so, what do you consider a match? Should two functions with the same code but different closures be considered equal? – Vlad Magdalin Jan 27 '13 at 01:52
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/23431/discussion-between-vlad-magdalin-and-pocesar) – Vlad Magdalin Jan 27 '13 at 01:53
14

As Vlad Magdalin pointed out in the comments, making the object to a JSON string, it can be as deep as it is, and functions and File/FileList class. Of course, instead of toString() on the function, it could just be called 'Function'

function replacer(k, v) {
    if (typeof v === 'function') {
        v = v.toString();
    } else if (window['File'] && v instanceof File) {
        v = '[File]';
    } else if (window['FileList'] && v instanceof FileList) {
        v = '[FileList]';
    }
    return v;
}

beforeEach(function(){
    this.addMatchers({
        toBeJsonEqual: function(expected){
            var one = JSON.stringify(this.actual, replacer).replace(/(\\t|\\n)/g,''),
                two = JSON.stringify(expected, replacer).replace(/(\\t|\\n)/g,'');

                return one === two;
            }
    });
});

expect(obj).toBeJsonEqual(obj2);
pocesar
  • 6,860
  • 6
  • 56
  • 88
  • 3
    Please note that JSON.stringify doesn't guarantee property order! So comparing generated JSONs might cause random test failures. It depends on the underlying javascript runner how properties will be sorted. For example: tests might work when run in engineA (e.g. NodeJS), but then break in engineB (e.g. Firefox) or even in another engineA version. Or break randomly even when run in the same engine of the same version. – jannis Apr 14 '17 at 10:33
5

If anyone is using node.js like myself, the following method is what I use in my Jasmine tests when I am only concerned with comparing the simple properties while ignoring all functions. This method requires json-stable-stringify which is used to sort the object properties prior to serializing.

Usage:

  var stringify = require('json-stable-stringify');

  var obj1 = {
    func: function() {
    },
    str1: 'str1 value',
    str2: 'str2 value',
    nest1: {
    nest2: {
        val1:'value 1',
        val2:'value 2',
        someOtherFunc: function() {
        }
      }
    }
  };

  var obj2 = {
    str2: 'str2 value',
    str1: 'str1 value',
    func: function() {
    },
    nest1: {
      nest2: {
        otherFunc: function() {
        },
        val2:'value 2',
        val1:'value 1'
      }
    }
  };

  it('should compare object properties', function () {
    expect(stringify(obj1)).toEqual(stringify(obj2));
  });
Mikt25
  • 695
  • 1
  • 9
  • 18
4

Extending @Vlad Magdalin's answer, this worked in Jasmine 2:

http://jasmine.github.io/2.0/custom_matcher.html

beforeEach(function() {
  jasmine.addMatchers({
    toDeepEqual: function(util, customEqualityTesters) {
      return {
        compare: function(actual, expected) {
          var result = {};
          result.pass = _.isEqual(actual, expected);
          return result;
        }
      }
    }
  });
});

If you're using Karma, put that in the startup callback:

callback: function() {
  // Add custom Jasmine matchers.
  beforeEach(function() {
    jasmine.addMatchers({
      toDeepEqual: function(util, customEqualityTesters) {
        return {
          compare: function(actual, expected) {
            var result = {};
            result.pass = _.isEqual(actual, expected);
            return result;
          }
        }
      }
    });
  });

  window.__karma__.start();
});
Aram Kocharyan
  • 20,165
  • 11
  • 81
  • 96
2

here's how I did it using the Jasmine 2 syntax.

I created a customMatchers module in ../support/customMatchers.js (I like making modules).

"use strict";

/**
 *  Custom Jasmine matchers to make unit testing easier.
 */
module.exports = {
  // compare two functions.
  toBeTheSameFunctionAs: function(util, customEqualityTesters) {
    let preProcess = function(func) {
      return JSON.stringify(func.toString()).replace(/(\\t|\\n)/g,'');
    };

    return {
      compare: function(actual, expected) {
        return {
          pass: (preProcess(actual) === preProcess(expected)),
          message: 'The functions were not the same'
        };
      }
    };
  }
}

Which is then used in my test as follows:

"use strict";

let someExternalFunction = require('../../lib/someExternalFunction');
let thingBeingTested = require('../../lib/thingBeingTested');

let customMatchers = require('../support/customMatchers');

describe('myTests', function() {

  beforeEach(function() {
    jasmine.addMatchers(customMatchers);

    let app = {
      use: function() {}
    };

    spyOn(app, 'use');
    thingBeingTested(app);
  });

  it('calls app.use with the correct function', function() {
    expect(app.use.calls.count()).toBe(1);
    expect(app.use.calls.argsFor(0)).toBeTheSameFunctionAs(someExternalFunction);
  });

});
Dave Sag
  • 13,266
  • 14
  • 86
  • 134
0

If you want to compare two objects but ignore their functions, you can use the methods _.isEqualWith together with _.isFunction from lodash as follows.

function ignoreFunctions(objValue, otherValue) {
  if (_.isFunction(objValue) && _.isFunction(otherValue)) {
    return true;
  }
}

it('check object equality but ignore their functions', () => {    
  ...
  expect(_.isEqualWith(actualObject, expectedObject, ignoreFunctions)).toBeTrue();
});
uminder
  • 23,831
  • 5
  • 37
  • 72