50

私たちのアプリには、ネストされたディレクティブのレイヤーがいくつかあります。トップレベルのディレクティブの単体テストを作成しようとしています。ディレクティブ自体が必要とするものをモックしましたが、下位レベルのディレクティブからエラーが発生しています。最上位ディレクティブの単体テストでは、下位レベルのディレクティブで何が起こっているかを心配する必要はありません。下位レベルのディレクティブをモックし、基本的に何もしないようにして、トップレベルのディレクティブを分離してテストできるようにしたいだけです。

次のようにして、ディレクティブ定義を上書きしようとしました。

angular.module("myModule").directive("myLowerLevelDirective", function() {
    return {
        link: function(scope, element, attrs) {
            //do nothing
        }
    }
});

ただし、これはそれを上書きするのではなく、実際のディレクティブに加えてこれを実行するだけです。これらの下位レベルのディレクティブが最上位のディレクティブの単体テストで何もしないようにするにはどうすればよいですか?

4

7 に答える 7

82

ディレクティブは単なるファクトリであるため、これを行う最善の方法はmodule、通常はbeforeEachブロック内で関数を使用してディレクティブのファクトリをモックすることです。do-something-else というディレクティブによって使用される do-something というディレクティブがあると仮定すると、次のようにモックします。

beforeEach(module('yourapp/test', function($provide){
  $provide.factory('doSomethingDirective', function(){ return {}; });
}));

// Or using the shorthand sytax
beforeEach(module('yourapp/test', { doSomethingDirective: {} ));

その後、テンプレートがテストでコンパイルされると、ディレクティブがオーバーライドされます

inject(function($compile, $rootScope){
  $compile('<do-something-else></do-something-else>', $rootScope.$new());
});

コンパイラがこれを内部的に行うため、名前に「ディレクティブ」サフィックスを追加する必要があることに注意してください:

于 2014-01-06T13:30:55.387 に答える
65

ディレクティブをモックするクリーンな方法は$compileProvider

beforeEach(module('plunker', function($compileProvider){
  $compileProvider.directive('d1', function(){ 
    var def = {
      priority: 100,
      terminal: true,
      restrict:'EAC',
      template:'<div class="mock">this is a mock</div>',
    };
    return def;
  });
}));

モックがモックしているディレクティブよりも高い優先度を取得し、モックがターミナルであることを確認して、元のディレクティブがコンパイルされないようにする必要があります。

priority: 100,
terminal: true,

結果は次のようになります。

このディレクティブが与えられた場合:

var app = angular.module('plunker', []);
app.directive('d1', function(){
  var def =  {
    restrict: 'E',
    template:'<div class="d1"> d1 </div>'
  }
  return def;
});

次のようにモックできます。

describe('testing with a mock', function() {
var $scope = null;
var el = null;

beforeEach(module('plunker', function($compileProvider){
  $compileProvider.directive('d1', function(){ 
    var def = {
      priority: 9999,
      terminal: true,
      restrict:'EAC',
      template:'<div class="mock">this is a mock</div>',
    };
    return def;
  });
}));

beforeEach(inject(function($rootScope, $compile) {
  $scope = $rootScope.$new();
  el = $compile('<div><d1></div>')($scope);
}));

it('should contain mocked element', function() {
  expect(el.find('.mock').length).toBe(1);
});
});

さらにいくつかのこと:

  • replace:trueモックを作成するときは、 and/orが必要かどうかを検討する必要がありますtemplate。たとえばng-src、バックエンドへの呼び出しを防ぐためにモックする場合replace:trueは、template. しかし、視覚的なものをモックする場合は、そうしたいかもしれません。

  • 100 を超える優先順位を設定すると、モックの属性は補間されません。$compile ソース コードを参照してください。たとえば、モックng-srcして を設定priority:101すると、最終的にモックでng-src="{{variable}}"はありませんng-src="interpolated-value"

これがすべてのプランカーです。正しい方向に向けてくれた@trodriguesに感謝します。

詳細を説明するドキュメントを次に示します。「構成ブロック」セクションを確認してください。@ebelanger に感謝!

于 2014-02-21T01:35:44.293 に答える
32

ディレクティブ登録の実装により、既存のディレクティブをモックされたディレクティブに置き換えることはできないようです。

ただし、下位レベルのディレクティブから干渉されることなく、上位レベルのディレクティブを単体テストする方法はいくつかあります。

1)単体テスト テンプレートで下位レベルのディレクティブを使用しないでください。

下位レベルのディレクティブが上位レベルのディレクティブによって追加されていない場合、単体テストでは、higer-level-directive のみを含むテンプレートを使用します。

var html = "<div my-higher-level-directive></div>";
$compile(html)(scope);

したがって、下位レベルのディレクティブは干渉しません。

2)ディレクティブの実装でサービスを使用します。

サービスによって、下位レベルのディレクティブ リンク機能を提供できます。

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        link: myService.lowerLevelDirectiveLinkingFunction
    }
});

次に、単体テストでこのサービスをモックして、より高いレベルのディレクティブとの干渉を回避できます。このサービスは、必要に応じてディレクティブ オブジェクト全体を提供することもできます。

3)下位レベルのディレクティブを端末ディレクティブで上書きできます:

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        priority: 100000,
        terminal: true,
        link: function() {
            // do nothing
        }
    }
});

端末オプションとより高い優先度を使用すると、実際の下位レベルのディレクティブは実行されません。詳細については、ディレクティブ docを参照してください。

このPlunkerでどのように機能するかを確認してください。

于 2013-07-19T16:27:01.317 に答える
5

内部のテンプレートを変更して$templateCache、下位レベルのディレクティブを削除できます。

beforeEach(angular.mock.inject(function ($templateCache) {
  $templateCache.put('path/to/template.html', '<div></div>');
}));
于 2013-12-27T13:33:37.560 に答える
3

これについてもっと自分で考えざるを得なくなったので、私たちのニーズを満たす解決策を思いつきました. ディレクティブはすべて属性であるためattributeRemover、単体テスト中に使用するディレクティブを作成しました。次のようになります。

angular.module("myModule").directive("attributeRemover", function() {
    return {
        priority: -1, //make sure this runs last
        compile: function(element, attrs) {
            var attributesToRemove = attrs.attributeRemover.split(",");
            angular.forEach(attributesToRemove, function(currAttributeToRemove) {
                element.find("div[" + currAttributeToRemove + "]").removeAttr(currAttributeToRemove);
            });
        }
    }
});

次に、テストしているディレクティブの html は次のようになります。

<div my-higher-level-directive attribute-remover="my-lower-level-directive,another-loweler-level-directive"></div>

したがって、my-higher-level-directiveコンパイルされるattribute-removerと、下位レベルのディレクティブの属性が既に削除されているため、それらが何をしているのかを心配する必要はありません。

おそらく、すべての種類のディレクティブ (属性ディレクティブだけでなく) に対してこれを行うより堅牢な方法があり、組み込みの JQLite のみを使用する場合にこれが機能するかどうかはわかりませんが、必要なものには機能します。

于 2013-07-19T16:10:16.640 に答える
1

ここに別の小さなアイデアがあります。このコードをジャスミン ヘルパー (コーヒー スクリプト) に入れるだけです。

window.mockDirective = (name, factoryFunction) ->
  mockModule = angular.module('mocks.directives', ['ng'])
  mockModule.directive(name, factoryFunction)

  module ($provide) ->
    factoryObject = angular.injector([mockModule.name]).get("#{name}Directive")
    $provide.factory "#{name}Directive", -> factoryObject
    null

そしてそれを使用します:

beforeEach mockDirective, "myLowerLevelDirective", ->
  link: (scope, element) ->

これにより、指定されたディレクティブの他のすべての実装が完全に削除され、ディレクティブに渡された引数をテストするための完全なアクセスが与えられます。たとえば、mm.foundation アラート ディレクティブは次のようにモックできます。

beforeEach mockDirective 'alert', ->
  scope:
    type: '='

そしてテストしました:

expect(element.find('alert').data('$isolateScopeNoTemplate').type).toEqual 
于 2015-08-07T10:00:43.557 に答える