web-dev-qa-db-fra.com

Angular js document de simulation de test unitaire

J'essaie de tester le service angular qui effectue quelques manipulations sur DOM via $document service au jasmin. Disons qu'il ajoute simplement une directive au <body> élément.

Un tel service pourrait ressembler à

(function(module) {
    module.service('myService', [
        '$document',
        function($document) {
            this.doTheJob = function() {
                $document.find('body').append('<my-directive></my directive>');
            };
        }
    ]);
})(angular.module('my-app'));

Et je veux le tester comme ça

describe('Sample test' function() {
    var myService;

    var mockDoc;

    beforeEach(function() {
        module('my-app');

        // Initialize mock somehow. Below won't work indeed, it just shows the intent
        mockDoc = angular.element('<html><head></head><body></body></html>');

        module(function($provide) {
            $provide.value('$document', mockDoc);
        });
    });

    beforeEach(inject(function(_myService_) {
        myService = _myService_;
    }));

    it('should append my-directive to body element', function() {
        myService.doTheJob();
        // Check mock's body to contain target directive
        expect(mockDoc.find('body').html()).toContain('<my-directive></my-directive>');
    });
});

La question est donc quelle serait la meilleure façon de créer une telle maquette?

Tester avec de vrais document nous donnera beaucoup de mal à nettoyer après chaque test et ne ressemble pas à un moyen de continuer.

J'ai également essayé de créer une nouvelle instance de document réel avant chaque test, mais je me suis retrouvé avec des échecs différents.

Créer un objet comme ci-dessous et vérifier la variable whatever fonctionne mais semble très moche

var whatever = [];
var fakeDoc = {
    find: function(tag) {
              if (tag == 'body') {
                  return function() {
                      var self = this;
                      this.append = function(content) {
                          whatever.add(content);
                          return self;
                      };
                  };
              } 
          }
}

Je sens que je manque quelque chose d'important ici et que je fais quelque chose de très mal.

Toute aide est très appréciée.

17
Alexander Nyrkov

Vous n'avez pas besoin de vous moquer du $document service dans un tel cas. Il est plus simple d'utiliser simplement son implémentation réelle:

describe('Sample test', function() {
    var myService;
    var $document;

    beforeEach(function() {
        module('plunker');
    });

    beforeEach(inject(function(_myService_, _$document_) {
        myService = _myService_;
        $document = _$document_;
    }));

    it('should append my-directive to body element', function() {
        myService.doTheJob();
        expect($document.find('body').html()).toContain('<my-directive></my-directive>');
    });
});

Plunker ici .

Si vous avez vraiment besoin de vous en moquer, alors je suppose que vous devrez le faire comme vous l'avez fait:

$documentMock = { ... }

Mais cela peut casser d'autres choses qui reposent sur le $document le service lui-même (une telle directive qui utilise createElement, par exemple).

[~ # ~] mise à jour [~ # ~]

Si vous devez restaurer le document dans un état cohérent après chaque test, vous pouvez faire quelque chose comme suit:

afterEach(function() {
    $document.find('body').html(''); // or $document.find('body').empty()
                                     // if jQuery is available
});

Plunker ici (J'ai dû utiliser un autre conteneur sinon les résultats de Jasmine ne seraient pas rendus).

Comme @AlexanderNyrkov l'a souligné dans les commentaires, Jasmine et Karma ont leurs propres trucs à l'intérieur de la balise body, et les effacer en vidant le corps du document ne semble pas être une bonne idée.

MISE À JOUR 2

J'ai réussi à me moquer partiellement du $document service afin que vous puissiez utiliser le document de page réel et restaurer tout à un état valide:

beforeEach(function() {
    module('plunker');

    $document = angular.element(document); // This is exactly what Angular does
    $document.find('body').append('<content></content>');

    var originalFind = $document.find;
    $document.find = function(selector) {
      if (selector === 'body') {
        return originalFind.call($document, 'body').find('content');
      } else {
        return originalFind.call($document, selector);
      }
    }

    module(function($provide) {
      $provide.value('$document', $document);
    });        
});

afterEach(function() {
    $document.find('body').html('');
});

Plunker ici .

L'idée est de remplacer la balise body par une nouvelle que votre SUT peut manipuler librement et que votre test peut effacer en toute sécurité à la fin de chaque spécification.

17
Michael Benford

Vous pouvez créer un document de test vide en utilisant DOMImplementation#createHTMLDocument() :

describe('myService', function() {
  var $body;

  beforeEach(function() {
    var doc;

    // Create an empty test document based on the current document.
    doc = document.implementation.createHTMLDocument();

    // Save a reference to the test document's body, for asserting
    // changes to it in our tests.
    $body = $(doc.body);

    // Load our app module and a custom, anonymous module.
    module('myApp', function($provide) {
      // Declare that this anonymous module provides a service
      // called $document that will supersede the built-in $document
      // service, injecting our empty test document instead.
      $provide.value('$document', $(doc));
    });

    // ...
  });

  // ...
});

Parce que vous créez un nouveau document vide pour chaque test, vous n'interférerez pas avec la page exécutant vos tests et vous n'aurez pas à nettoyer explicitement après votre service entre les tests.

6
Ian Lesperance