AngularJS + Жасмин: сравнение объектов
Я только начинаю писать тесты для своего приложения AngularJS и делаю это в 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');
});
});
Сбой теста:
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, любые комментарии о том, устанавливаю ли я свой тест неправильно или его можно улучшить, будут приветствоваться.
Update:
Включая 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'}, {
});
});
Кроме того, я столкнулся с проблемой, сравнив идентификаторы вместо:
it('should preselect first client in array', function () {
expect(scope.selected.client.id).toEqual(RESPONSE[0].id);
});
Ответы
Ответ 1
toEqual
делает глубокое сравнение равенства. Это означает, что когда все свойства значений объектов равны, объекты считаются равными.
Как вы сказали, вы используете ресурс, который добавляет пару свойств к объектам в массиве.
Итак, это {id:12}
становится таким {id:12, $then: function, $resolved: true}
, которое не равно. Проверка идентификатора должна быть хорошей, если вы просто проверяете, правильно ли заданы значения.
Ответ 2
Краткий ответ:
Существующие ответы все рекомендуют либо строгать ваши объекты, либо создавать пользовательскую функцию сопоставления/сравнения. Но есть более простой способ: используйте angular.equals()
в своем вызове Jasmine expect
вместо использования встроенного toEqual
шаблона Jasmine.
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 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()
и .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
дает недопустимые данные, которые дадут ложный результат.
От взгляда на выше, здесь резюме различных методов:
-
expect(scope.foobar).toEqual(foobar)
: Это не работает в обоих направлениях. При сравнении объектов toString показывает, что Angular добавил дополнительные свойства. При сравнении массивов содержимое кажется идентичным, но этот метод по-прежнему утверждает, что он отличается.
-
expect(scope.foo.toString()).toEqual(myFooObject.toString())
: Это проходит в обоих направлениях. Однако это ложно позитивно, поскольку объекты не переводится полностью. Единственное утверждение, которое это делает, состоит в том, что два аргумента имеют одинаковое количество объектов.
-
expect(JSON.stringify(scope.foo)).toEqual(JSON.stringify(myFooObject))
: этот метод дает правильный ответ при сравнении массивов, но сравнение объектов имеет аналогичную ошибку с исходным сравнением.
-
expect(angular.equals(scope.foo, myFooObject)).toBe(true)
: Это правильный способ сделать утверждение. Предоставляя Angular выполнить сравнение, он знает, что игнорировать любые свойства, которые были добавлены в бэкэнд, и дает правильное результат.
Если это важно для любого, я использую AngularJS 1.2.14 и Karma 0.10.10 и тестирование на PhantomJS 1.9.7.
Ответ 3
Короче говоря: добавьте 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]);
});
Ответ 4
У меня была аналогичная проблема, и на основе многих подходов был реализован пользовательский совпадение:
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);
});
Конечно, это очень простой помощник и не поддерживает многие случаи, но мне не нужно ничего более сложного. Вы можете обернуть сокеты в файл конфигурации.
Отметьте этот ответ для аналогичной реализации.
Ответ 5
У меня была такая же проблема, поэтому я просто назовет JSON.stringify()
для сравниваемых объектов.
expect( JSON.stringify( $scope.angularResource ) == JSON.stringify( expectedValue )).toBe( true );
Ответ 6
Немного подробный, но при возникновении ожидания генерирует полезное сообщение:
expect(JSON.parse(angular.toJson(resource))).toEqual({ id: 1 });
Пояснение:
angular.toJson
разделит ресурс всех angular определенных свойств, таких как $promise
JSON.parse
преобразует строку JSON обратно в обычный объект (или массив), который теперь можно сравнить с другим объектом (или массивом).