AngularJS + Jasmine:比较对象

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

这里是相关的代码片段

ClientController:

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

ClientControllerSpec:

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

testing失败:

 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) 

有没有人有任何想法,为什么这可能会发生?

另外..因为我是新来写AngularJStesting,任何意见是否设置我的testing错误或者是否可以改善将是受欢迎的。

更新:

包括ClientService:

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

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

正如你所说的,你正在使用资源,它向数组中的对象添加了一些属性。

所以这个{id:12}变成这个{id:12, $then: function, $resolved: true} ,这是不相等的。 身份证检查应该没问题,如果你只是testing,如果你正确地设置值。

简短的回答:

现有的答案都build议将对象串化,或者创build自定义的匹配器/比较函数。 但是,有一个更简单的方法:在Jasmine expect调用中使用angular.equals() ,而不是使用Jasmine内置的toEqual匹配器。

angular.equals()将忽略通过Angular添加到对象的附加属性,而toEqual则不能通过$promise存在于其中一个对象上进行比较。


更长的解释:

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

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

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

作为参考,以下是使用JSON.stringify().toString() console.log的输出:

 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]' 

注意string化的对象是如何具有额外的属性,以及如何产生无效的数据,这将给出一个误报。

从上面看,这里是不同方法的总结:

  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上进行testing。

长话短说:添加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]); }); 

我只是有一个类似的问题,并根据以下方法实现了一个自定义的匹配器:

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

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

检查这个答案的类似的实现。

我有同样的问题,所以我只是在要比较的对象上调用JSON.stringify()

 expect( JSON.stringify( $scope.angularResource ) == JSON.stringify( expectedValue )).toBe( true ); 

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

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

说明:

angular.toJson将剥离所有angular度特定属性(如$promise

JSON.parse会将JSONstring转换回正常的Object(或Array),现在可以将其与另一个Object(或Array)进行比较。