web-dev-qa-db-fra.com

AngularJS + Jasmine: Comparaison d'objets

Je commence tout juste à écrire des tests pour mon application AngularJS et je le fais dans Jasmine.

Voici les extraits de code pertinents

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

Le test échoue:

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) 

Quelqu'un at-il une idée de la raison pour laquelle cela pourrait se produire?

Aussi .. comme je suis nouveau dans l'écriture de tests AngularJS, tout commentaire sur la mauvaise configuration de mon test ou son amélioration sera le bienvenu.

Mise à jour:

Y compris 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'}, {

    });
});

De plus, j'ai contourné le problème en comparant les identifiants à la place:

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

toEqual fait une comparaison approfondie de l'égalité. Ce qui signifie que lorsque toutes les propriétés des valeurs des objets sont égales, les objets sont considérés comme égaux.

Comme vous l'avez dit, vous utilisez une ressource qui ajoute quelques propriétés aux objets du tableau.

Donc ça {id:12} devient ceci {id:12, $then: function, $resolved: true} qui ne sont pas égaux. La vérification des identifiants devrait être correcte si vous testez simplement si vous définissez les valeurs correctement.

64
Umur Kontacı

Réponse courte:

Les réponses existantes recommandent toutes soit de filtrer vos objets, soit de créer une fonction de comparaison/comparaison personnalisée. Mais, il existe un moyen plus simple: utilisez angular.equals() dans votre appel Jasmine expect, au lieu d'utiliser le matcher toEqual intégré de Jasmine.

angular.equals() ignorera les propriétés supplémentaires ajoutées à vos objets par Angular, tandis que toEqual échouera la comparaison pour, disons, $promise se trouvant sur l'un des objets.


Explication plus longue:

J'ai rencontré ce même problème dans mon application AngularJS. Fixons le scénario:

Dans mon test, j'ai créé un objet local et un tableau local, et je les attendais comme réponses à deux demandes GET. Ensuite, j'ai comparé le résultat des GET avec l'objet et le tableau d'origine. J'ai testé cela en utilisant quatre méthodes différentes, et une seule a donné des résultats appropriés.

Voici une partie de 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
});

Pour référence, voici la sortie de console.log En utilisant JSON.stringify() et .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]'

Remarquez comment l'objet chaîne a des propriétés supplémentaires et comment toString produit des données invalides qui donneront un faux positif.

En regardant ce qui précède, voici un résumé des différentes méthodes:

  1. expect(scope.foobar).toEqual(foobar): Cela échoue dans les deux sens. Lors de la comparaison d'objets, toString révèle que Angular a ajouté des propriétés supplémentaires. Lors de la comparaison de tableaux, le contenu semble identique, mais cette méthode prétend toujours qu'ils sont différents.
  2. expect(scope.foo.toString()).toEqual(myFooObject.toString()): Cela passe dans les deux sens. Cependant, c'est un faux positif, car les objets ne sont pas entièrement traduits. La seule affirmation que cela fait est que les deux arguments ont le même nombre d'objets.
  3. expect(JSON.stringify(scope.foo)).toEqual(JSON.stringify(myFooObject)): Cette méthode donne la bonne réponse lors de la comparaison de tableaux, mais la comparaison d'objets a un défaut similaire à la comparaison brute.
  4. expect(angular.equals(scope.foo, myFooObject)).toBe(true): C'est la bonne façon de faire l'assertion. En laissant Angular faire la comparaison, il sait ignorer toutes les propriétés qui ont été ajoutées dans le backend, et donne le bon résultat.

Si cela importe pour tout le monde, j'utilise AngularJS 1.2.14 et Karma 0.10.10, et je teste sur PhantomJS 1.9.7.

56
The DIMM Reaper

Pour faire court: ajoutez angular.equals comme matcher de jasmin.

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

Vous pouvez donc l'utiliser comme suit:

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]);
});
14
Adrian

Je viens d'avoir un problème similaire et j'ai implémenté un matcher personnalisé comme suit, basé sur de nombreuses approches:

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

    }
  });
});

puis utilisé de cette façon:

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

Bien sûr, c'est un matcher très simple et ne prend pas en charge de nombreux cas, mais je n'avais besoin de rien de plus complexe que cela. Vous pouvez envelopper les matchers dans un fichier de configuration.

Vérifiez cette réponse pour une implémentation similaire.

6
Pipetus

J'ai eu le même problème donc j'ai juste appelé JSON.stringify() sur les objets à comparer.

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

Un peu verbeux, mais produit un message utile lorsque l'attente échoue:

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

Explication:

angular.toJson supprimera la ressource de toutes les propriétés spécifiques angular comme $promise

JSON.parse reconvertira la chaîne JSON en objet (ou tableau) normal, qui peut maintenant être comparé à un autre objet (ou tableau).

0
cute_ptr