Function.prototype.bind()の戻り値をsinon.jsでテストする方法
こんにちは。最近はmochaに変えてテストを頑張っています。今回はFunction.prototype.bind()
の戻り値をテストする方法についてです。
どういう状況?
function Directive() {
// ..
}
Directive.prototype.compile = function(elem, attrs, transclude) {
return this.link.bind(this);
};
Directive.prototype.link = function($scope, elem, attrs) {
// ...
};
AngularJSでの一幕ですが、APIの仕様上compile()
がlink()
を返すことで2つの処理が行われるようになっています。私はTypeScriptで書いているので、あまりオブジェクトと関数のネストを好まずクラスとメソッドで書くため、このようなprototype
の多用が起こります。
何をテストしたい?
Directive#compile()
がDirective#link()
を「実行するのではなく、第一級関数として返す」ことを検証したいのです。
考えたアプローチ
以前書いた拙記事に「Function型の変数同士の比較」というものがありました。今回も同じようにString()
で文字列にして検証してやろうとしたわけです。(少し行儀悪い気もしますが)
これには問題がありました。.bind()
をすると結果の関数オブジェクトに対してString()
を行っても"[native code]"
しか返さないのです。もちろん検証は不可能。
Sinon.JSのstubを使ってみた
一か八か、Sinon.JSでなんとかしようと試みました。結果としては成功です。
var Directive: any;
beforeEach(() => {
Directive = new DirectiveStatic();
});
// ...
it('compile() は link() 関数を返す', () => {
var DirectiveStub = {
link: sinon.stub(Directive, 'link')
};
var result = Directive.compile(elem, attrs, transclude);
result();
assert(DirectiveStub.link.callCount === 1);
});
beforeEach()
でDirective
のインスタンスを生成しますが、it()
の段階でスパイを作っても全然問題ないようです。DirectiveStatic
というのは、実装側からインポートする際の変数名です。気にしないで。
sinon
にはsinon.spy
とsinon.stub
という2つの代表的な使用法がありますが、ここでspy
にしてしまうと、このテストでは求めていないDirective#link()
の処理まで走ってしまいます。そうさせないためにstub
とします。
あとは受け取ったresult
に()
を付けて実行させれば完了。callCount
を検証します。ここでresult(); result();
とするとcallCount
は2になり、何もしないと0のままなので、検証の意味は果たしていると判断しました。
どうやら戻り値がthis
(つまりDirective
インスタンス)でバインドしたあとの関数だったおかげでスタブが活きたようです。面白いなー。
ニッチなテクですが、もしこの問題に直面したら思い出してみてください。
Discussion