web-dev-qa-db-ja.com

Angular js unit test mock document

私はangularサービスをテストしようとしています。ジャスミンで$documentサービスを介してDOMにいくつかの操作を行います。それは<body>要素にいくつかのディレクティブを追加するだけだとしましょう。

このようなサービスは次のようになります

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

このようにテストしたい

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

だから問題はそのようなモックを作成するための最良の方法は何でしょうか?

実際のdocumentを使用してテストすると、各テスト後にクリーンアップに多くの問題が発生し、適切な方法ではないように見えます。

また、各テストの前に新しい実際のドキュメントインスタンスを作成しようとしましたが、さまざまなエラーが発生しました。

以下のようなオブジェクトを作成し、whatever変数をチェックすることは機能しますが、非常に醜く見えます

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

私はここで重要な何かを見逃していて、何か非常に間違っていると感じています。

どんな助けでも大歓迎です。

17

そのような場合、$documentサービスをモックする必要はありません。実際の実装を使用する方が簡単です。

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

プランカー ここ

あなたが本当にそれをあざける必要があるなら、私はあなたがあなたがした方法でそれをしなければならないでしょうね:

$documentMock = { ... }

しかし、それは$documentサービス自体に依存する他のもの(たとえば、createElementを使用するディレクティブなど)を壊す可能性があります。

[〜#〜]更新[〜#〜]

各テスト後にドキュメントを一貫性のある状態に戻す必要がある場合は、次のように実行できます。

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

Plunker here (別のコンテナを使用する必要がありました。そうしないと、Jasmineの結果が表示されませんでした)。

@AlexanderNyrkovがコメントで指摘したように、JasmineとKarmaの両方がbodyタグ内に独自のものを持っているため、ドキュメントの本文を空にすることでそれらを一掃することは良い考えのようには思えません。

更新2

実際にページドキュメントを使用してすべてを有効な状態に復元できるように、$documentサービスを部分的にモックすることに成功しました。

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

プランカー ここ

アイデアは、bodyタグを、SUTが自由に操作でき、すべての仕様の最後で安全にクリアできる新しいタグに置き換えることです。

17
Michael Benford

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

    // ...
  });

  // ...
});

テストごとに新しい空のドキュメントを作成するので、テストを実行しているページに干渉したり、テスト間のサービスの後で明示的にクリーンアップしたりする必要はありません。

6
Ian Lesperance