申し分なく、私は長い間いくつかの問題につまずいてきました、そして私はコミュニティの他の人々から意見を聞きたいです。
最初に、いくつかの抽象的なコントローラーを見てみましょう。
_function Ctrl($scope, anyService) {
$scope.field = "field";
$scope.whenClicked = function() {
util();
};
function util() {
anyService.doSmth();
}
}
_
明らかにここにあります:
$scope
_といくつかのサービスが注入されたコントローラーの通常の足場util()
ここで、このクラスを単体テストでカバーしたいと思います(Jasmine)。ただし、問題は、[whenClicked()
]メソッドが呼び出されるアイテムをクリックする(util()
を呼び出す)ことを確認することです。 Jasmineテストでは、util()
のモックが定義されていないか、呼び出されていないというエラーが常に表示されるため、その方法はわかりません。
注:この特定の例を修正しようとはしていませんが、このようなコードパターンを一般的にテストすることを求めています。それ、これを修正する方法ではありません。
私はこれについていくつかの方法を試してきました:
$scope
_を使用できないことは明らかです(通常、メッセージ_Expected spy but got undefined
_または同様のメッセージで終了します)Ctrl.util = util;
_を介してこれらの関数をコントローラーオブジェクトにアタッチし、Ctrl.util = jasmine.createSpy()
のようなモックを検証しようとしましたが、この場合は_Ctrl.util
_が呼び出されないためテストが失敗しますutil()
を変更してthis
オブジェクトにアタッチし、_Ctrl.util
_を再びモックしようとしましたが、運がありませんまあ、私はこれを回避する方法を見つけることができません、JS忍者からの助けを期待します、作業フィドルは完璧でしょう。
スコープに名前空間を置くことは汚染です。やりたいことは、そのロジックを別の関数に抽出し、それをコントローラーに注入することです。つまり.
function Ctrl($scope, util) {
$scope.field = "field";
$scope.whenClicked = function() {
util();
};
}
angular.module("foo", [])
.service("anyService", function(...){...})
.factory("util", function(anyService) {
return function() {
anyService.doSmth();
};
});
これで、Ctrl 同様に "util"をモックで単体テストできます。
指定したコントローラー関数は、コンストラクターとしてAngular=によって使用されます。ある時点で、実際のコントローラーインスタンスを作成するためにnew
で呼び出されます。コントローラーオブジェクトに関数が本当に必要な場合これらは$ scopeには公開されませんが、スパイ/スタブ/モックに使用できます。これらはthis
にアタッチできます。
_function Ctrl($scope, anyService) {
$scope.field = "field";
$scope.whenClicked = function() {
util();
};
this.util = function() {
anyService.doSmth();
}
}
_
var ctrl = new Ctrl(...)
を呼び出すか、Angular _$controller
_)サービスを使用してCtrl
インスタンスを取得すると、返されるオブジェクトにはutil
関数が含まれます。
このアプローチはここで見ることができます: http://jsfiddle.net/yianisn/8P9Mv/
別のアプローチでチャイムします。プライベートメソッドをテストするべきではありません。それがプライベートな理由です-使用法とは無関係な実装の詳細です。
たとえば、utilが複数の場所で使用されていたが、現在は他のコードリファクタリングに基づいて、この1つの場所でのみ呼び出されることに気付いた場合はどうなるでしょう。なぜ追加の関数呼び出しがあるのですか? anyService.doSmith()
をインクルードするだけ$scope.whenClicked()
上記の提案では、util()
が呼び出されることをテストしていると仮定すると、テストを変更していなくてもテストは中断しますプログラムの機能。単体テストの主な価値の1つは、物事を壊さずにリファクタリングを簡素化することです。したがって、物事を壊さなかった場合、テストは失敗しないはずです。
必要なことは、_$scope.whenClicked
_が呼び出されたときに、anyService.doSmth()
も呼び出されるようにすることです。あなただけが必要です:
_spyOn(anyService,'doSmith')
scope.whenClicked();
expect(anyService.doSmith).toHaveBeenCalled();
_
私は現在のアプローチを含む回答を追加し、いくつかのコメントを得て、おそらくこれが良い解決策であるかどうかについて議論を刺激したいと思っています。
コントローラー関数にプライベート関数を追加しています(したがって、それらをパブリックにし、モックを有効にします)。コントローラー名を常に繰り返す必要がなく、構文をより魅力的にするために、コントローラー関数への参照を保持するself
オブジェクトを作成しています。したがって、次のようになります。
function Ctrl($scope, anyService) {
$scope.field = "field";
$scope.whenClicked = function() {
self.util();
};
var self = Ctrl; // For the sake of syntax simplicity only
self.util = function() {
anyService.doSmth();
};
}
そして、単体テストで次を使用できます。
Ctrl.util = jasmine.createSpy("util()");
expect(Ctrl.util).toHaveBeenCalled();
私はまだこれがあまり好きではありませんが、これはこれを行う最も簡単な方法だと思います。私は誰かがより良いアプローチを見つけることを望んでいます。