44

我刚刚开始为我的 AngularJS 应用程序编写测试,并且正在 Jasmine 中这样做。

以下是相关的代码片段

客户端控制器:

'use strict';

adminConsoleApp.controller('ClientController',
    function ClientController($scope, Client) {

        //Get list of clients
        $scope.clients = Client.query(function () {
            //preselect first client in array
            $scope.selected.client = $scope.clients[0];
        });

        //necessary for data-binding so that it is accessible in child scopes.
        $scope.selected = {};

        //Current page
        $scope.currentPage = 'start.html';

        //For Client nav bar
        $scope.clientNavItems = [
            {destination: 'features.html', title: 'Features'},
        ];

        //Set current page
        $scope.setCurrent = function (title, destination) {
            if (destination !== '') {
                $scope.currentPage = destination;
            }

        };

        //Return path to current page
        $scope.getCurrent = function () {
            return 'partials/clients/' + $scope.currentPage;
        };

        //For nav bar highlighting of active page
        $scope.isActive = function (destination) {
            return $scope.currentPage === destination ? true : false;
        };

        //Reset current page on client change
        $scope.clientChange = function () {
            $scope.currentPage = 'start.html';
        };
    });

客户端控制器规范:

'use strict';

var RESPONSE = [
    {
        "id": 10,
        "name": "Client Plus",
        "ref": "client-plus"
    },
    {
        "id": 13,
        "name": "Client Minus",
        "ref": "client-minus"
    },
    {
        "id": 23805,
        "name": "Shaun QA",
        "ref": "saqa"
    }
];

describe('ClientController', function() {

    var scope;

    beforeEach(inject(function($controller, $httpBackend, $rootScope) {
        scope = $rootScope;
        $httpBackend.whenGET('http://localhost:3001/clients').respond(RESPONSE);
        $controller('ClientController', {$scope: scope});
        $httpBackend.flush();
    }));

    it('should preselect first client in array', function() {
        //this fails.
        expect(scope.selected.client).toEqual(RESPONSE[0]);
    });

    it('should set current page to start.html', function() {
        expect(scope.currentPage).toEqual('start.html');
    });
});

测试失败:

Chrome 25.0 (Mac) ClientController should preselect first client in array FAILED
    Expected { id : 10, name : 'Client Plus', ref : 'client-plus' } to equal { id : 10, name : 'Client Plus', ref : 'client-plus' }.
    Error: Expected { id : 10, name : 'Client Plus', ref : 'client-plus' } to equal { id : 10, name : 'Client Plus', ref : 'client-plus' }.
        at null.<anonymous> (/Users/shaun/sandbox/zong-admin-console-app/test/unit/controllers/ClientControllerSpec.js:43:39) 

有没有人知道为什么会发生这种情况?

另外 .. 由于我是编写 AngularJS 测试的新手,欢迎对我是否设置测试错误或是否可以改进提出任何评论。

更新:

包括客户端服务:

'use strict';

AdminConsoleApp.services.factory('Client', function ($resource) {
    //API is set up such that if clientId is passed in, will retrieve client by clientId, else retrieve all.
    return $resource('http://localhost:port/clients/:clientId', {port: ':3001', clientId: '@clientId'}, {

    });
});

另外,我通过比较 id 解决了这个问题:

it('should preselect first client in array', function () {
    expect(scope.selected.client.id).toEqual(RESPONSE[0].id);
});
4

6 回答 6

69

toEqual进行深度相等比较。这意味着当对象值的所有属性都相等时,对象被认为是相等的。

正如您所说,您正在使用资源,它为数组中的对象添加了几个属性。

所以这{id:12}变成了这个{id:12, $then: function, $resolved: true}不相等的。如果您只是测试是否正确设置了值,则 ID 检查应该没问题。

于 2013-03-18T23:35:59.640 回答
57

简短的回答:

现有的答案都建议对您的对象进行字符串化,或创建自定义匹配器/比较函数。但是,有一种更简单的方法:angular.equals()在 Jasmineexpect调用中使用,而不是使用 Jasmine 的内置toEqual匹配器。

angular.equals()将忽略 Angular 添加到您的对象的附加属性,而toEqual将无法进行比较,例如,$promise在其中一个对象上。


更长的解释:

我在 AngularJS 应用程序中遇到了同样的问题。让我们设置场景:

在我的测试中,我创建了一个本地对象和一个本地数组,并期望它们作为对两个 GET 请求的响应。之后,我将 GET 的结果与原始对象和数组进行了比较。我使用四种不同的方法对此进行了测试,只有一种给出了正确的结果。

这是 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
});

作为参考,这是console.log使用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]'

请注意字符串化对象如何具有额外的属性,以及如何toString产生无效数据,这将导致误报。

综上所述,以下是对不同方法的总结:

  1. expect(scope.foobar).toEqual(foobar): 这两种方式都失败了。在比较对象时,toString 表明 Angular 添加了额外的属性。比较数组时,内容似乎相同,但这种方法仍然声称它们是不同的。
  2. expect(scope.foo.toString()).toEqual(myFooObject.toString()): 这是双向的。然而,这是一个误报,因为对象没有被完全翻译。唯一的断言是这两个参数具有相同数量的对象。
  3. expect(JSON.stringify(scope.foo)).toEqual(JSON.stringify(myFooObject)):此方法在比较数组时给出了正确的响应,但对象比较与原始比较有类似的错误。
  4. expect(angular.equals(scope.foo, myFooObject)).toBe(true):这是做出断言的正确方法。通过让 Angular 进行比较,它知道忽略后端添加的任何属性,并给出正确的结果。

如果这对任何人都很重要,我正在使用 AngularJS 1.2.14 和 Karma 0.10.10,并在 PhantomJS 1.9.7 上进行测试。

于 2014-10-14T21:06:36.920 回答
14

长话短说:添加angular.equals为茉莉花匹配器。

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

因此,您可以按如下方式使用它:

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]);
});
于 2014-10-15T14:19:22.783 回答
7

我刚刚遇到了类似的问题,并基于多种方法实现了一个自定义匹配器,如下所示:

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);

    }
  });
});

然后以这种方式使用:

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

当然,它是一个非常简单的匹配器,不支持很多情况,但我不需要比这更复杂的东西。您可以将匹配器包装在配置文件中。

检查此答案以获取类似的实现。

于 2013-05-28T19:00:29.000 回答
2

我遇到了同样的问题,所以我只调用JSON.stringify()了要比较的对象。

expect( JSON.stringify( $scope.angularResource ) == JSON.stringify( expectedValue )).toBe( true );
于 2014-05-06T06:01:17.573 回答
0

有点冗长,但在期望失败时会产生有用的信息:

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

解释:

angular.toJson将剥离所有角度特定属性的资源,例如$promise

JSON.parse将 JSON 字符串转换回普通对象(或数组),现在可以将其与另一个对象(或数组)进行比较。

于 2017-07-26T23:02:44.483 回答