AngularJS + Jasmine: Comparing objects

前端 未结 6 1632
隐瞒了意图╮
隐瞒了意图╮ 2020-12-15 03:18

I\'m just starting out writing tests for my AngularJS app and am doing so in Jasmine.

Here are the relevant code snippets

ClientController:

\         


        
相关标签:
6条回答
  • 2020-12-15 03:37

    Short answer:

    The existing answers all recommend either stringifying your objects, or creating a custom matcher/comparison function. But, there is an easier way: use angular.equals() in your Jasmine expect call, instead of using Jasmine's built-in toEqual matcher.

    angular.equals() will ignore the additional properties added to your objects by Angular, whereas toEqual will fail the comparison for, say, $promise being on one of the objects.


    Longer explanation:

    I ran across this same problem in my AngularJS application. Let's set the scenario:

    In my test, I created a local object and a local array, and expected them as responses to two GET requests. Afterwards, I compared the result of the GET's with the original object and array. I tested this using four different methods, and only one gave proper results.

    Here's a portion of foobar-controller-spec.js:

    var myFooObject = {id: 1, name: "Steve"};
    var myBarsArray = [{id: 1, color: "blue"}, {id: 2, color: "green"}, {id: 3, color: "red"}];
    
    ...
    
    beforeEach(function () {
        httpBackend.expectGET('/foos/1').respond(myFooObject);
        httpBackend.expectGET('/bars').respond(myBarsArray);
        
        httpBackend.flush();
    });
    
    it('should put foo on the scope', function () {
        expect(scope.foo).toEqual(myFooObject);
        //Fails with the error: "Expected { id : 1, name : 'Steve', $promise : { then : Function, catch : Function, finally : Function }, $resolved : true } to equal { id : 1, name : 'Steve' }."
        //Notice that the first object has extra properties...
        
        expect(scope.foo.toString()).toEqual(myFooObject.toString());
        //Passes, but invalid (see below)
        
        expect(JSON.stringify(scope.foo)).toEqual(JSON.stringify(myFooObject));
        //Fails with the error: "Expected '{"id":1,"name":"Steve","$promise":{},"$resolved":true}' to equal '{"id":1,"name":"Steve"}'."
        
        expect(angular.equals(scope.foo, myFooObject)).toBe(true);
        //Works as expected
    });
    
    it('should put bars on the scope', function () {
        expect(scope.bars).toEqual(myBarsArray);
        //Fails with the error: "Expected [ { id : 1, color : 'blue' }, { id : 2, color : 'green' }, { id : 3, color : 'red' } ] to equal [ { id : 1, color : 'blue' }, { id : 2, color : 'green' }, { id : 3, color : 'red' } ]."
        //Notice, however, that both arrays seem identical, which was the OP's problem as well.
        
        expect(scope.bars.toString()).toEqual(myBarsArray.toString());
        //Passes, but invalid (see below)
        
        expect(JSON.stringify(scope.bars)).toEqual(JSON.stringify(myBarsArray));
        //Works as expected
        
        expect(angular.equals(scope.bars, myBarsArray)).toBe(true);
        //Works as expected
    });
    

    For reference, here's the output from console.log using JSON.stringify() and .toString():

    LOG: '***** myFooObject *****'
    LOG: 'Stringified:{"id":1,"name":"Steve"}'
    LOG: 'ToStringed:[object Object]'
    
    LOG: '***** scope.foo *****'
    LOG: 'Stringified:{"id":1,"name":"Steve","$promise":{},"$resolved":true}'
    LOG: 'ToStringed:[object Object]'
    
    
    
    LOG: '***** myBarsArray *****'
    LOG: 'Stringified:[{"id":1,"color":"blue"},{"id":2,"color":"green"},{"id":3,"color":"red"}]'
    LOG: 'ToStringed:[object Object],[object Object],[object Object]'
    
    LOG: '***** scope.bars *****'
    LOG: 'Stringified:[{"id":1,"color":"blue"},{"id":2,"color":"green"},{"id":3,"color":"red"}]'
    LOG: 'ToStringed:[object Object],[object Object],[object Object]'
    

    Notice how the stringified object has extra properties, and how toString yields invalid data which will give a false positive.

    From looking at the above, here's a summary of the different methods:

    1. expect(scope.foobar).toEqual(foobar) : This fails both ways. When comparing objects, toString reveals that Angular has added extra properties. When comparing arrays, the contents seem identical, but this method still claims they are different.
    2. expect(scope.foo.toString()).toEqual(myFooObject.toString()) : This passes both ways. However, this is a false positive, since the objects are not being translated fully. The only assertion this makes is that the two arguments have the same number of objects.
    3. expect(JSON.stringify(scope.foo)).toEqual(JSON.stringify(myFooObject)) : This method gives the proper response when comparing arrays, but the object comparison has a similar fault to the raw comparison.
    4. expect(angular.equals(scope.foo, myFooObject)).toBe(true) : This is the correct way to make the assertion. By letting Angular do the comparison, it knows to ignore any properties which were added in the backend, and gives the proper result.

    If it matters to anyone, I'm using AngularJS 1.2.14 and Karma 0.10.10, and testing on PhantomJS 1.9.7.

    0 讨论(0)
  • 2020-12-15 03:50

    toEqual makes a deep equality comparison. Which means that when all the properties of the objects' values are equal, the objects are considered to be equal.

    As you said, you are using resource which adds a couple of properties to the objects in the array.

    So this {id:12} becomes this {id:12, $then: function, $resolved: true} which are not equal. Id checking should be fine if you are just testing if you set the values properly.

    0 讨论(0)
  • 2020-12-15 03:50

    Long story short: add angular.equals as a jasmine matcher.

    beforeEach(function(){
      this.addMatchers({
        toEqualData: function(expected) {
          return angular.equals(this.actual, expected);
        }
      });
    });
    

    So, then you can use it as follows:

    it('should preselect first client in array', function() {
        //this passes:
        expect(scope.selected.client).toEqualData(RESPONSE[0]);
    
        //this fails:
        expect(scope.selected.client).toEqual(RESPONSE[0]);
    });
    
    0 讨论(0)
  • 2020-12-15 04:01

    I had the same problem so I just called JSON.stringify() on the objects to be compared.

    expect( JSON.stringify( $scope.angularResource ) == JSON.stringify( expectedValue )).toBe( true );
    
    0 讨论(0)
  • 2020-12-15 04:02

    I just had a similar problem and implemented a custom matcher as follows, based on many approaches:

    beforeEach(function() {
      this.addMatchers({
        toBeSimilarTo: function(expected) {
          function buildObject(object) {
            var built = {};
            for (var name in object) {
              if (object.hasOwnProperty(name)) {
                built[name] = object[name];
              }
            }
            return built;
          }
    
          var actualObject = buildObject(this.actual);
          var expectedObject = buildObject(expected);
          var notText = this.isNot ? " not" : "";
    
          this.message = function () {
            return "Expected " + actualObject + notText + " to be similar to " + expectedObject;
          }
    
          return jasmine.getEnv().equals_(actualObject, expectedObject);
    
        }
      });
    });
    

    and then used this way:

    it("gets the right data", function() {
      expect(scope.jobs[0]).toBeSimilarTo(myJob);
    });
    

    Of course, it's a very simple matcher and doesn't support many cases, but I didn't need anything more complex than that. You can wrap the matchers in a config file.

    Check this answer for a similar implementation.

    0 讨论(0)
  • 2020-12-15 04:02

    A little bit verbose, but produces helpful message when expectation fails:

    expect(JSON.parse(angular.toJson(resource))).toEqual({ id: 1 });
    

    Explanation:

    angular.toJson will strip the resource of all angular specific properties like $promise

    JSON.parse will convert JSON string back to normal Object (or Array), which can now be compared to another Object (or Array).

    0 讨论(0)
提交回复
热议问题