AngularJS 1.XプロジェクトでTypeScriptを使用しています。さまざまな目的でさまざまなJavascriptライブラリを使用しています。ソースを単体テストするには、Typings(=インターフェイス)を使用していくつかの依存関係をスタブ化します。 ANY-typeを使用したり、各インターフェイスメソッドに空のメソッドを記述したりする必要はありません。
私はそのようなことをする方法を探しています:
let dependency = stub(IDependency);
stub(dependency.b(), () => {console.log("Hello World")});
dependency.a(); // --> Compile, do nothing, no exception
dependency.b(); // --> Compile, print "Hello World", no exception
私が今苦しんでいるのは、any
を使用してテストケースで呼び出されるすべてのメソッドを実装するか、インターフェイスを実装して完全なインターフェイスを実装することです。それはあまりにも役に立たないコードです:(。
各メソッドの空の実装を持ち、型指定されたオブジェクトを生成するにはどうすればよいですか?私はSinonをモックの目的で使用していますが、他のライブラリも使用できます。
PS:TypeScriptがインターフェイスを消去することは知っていますが、それでも解決したいと思います:)。
簡単な答えは、これはTypeScriptでは不可能であると思う、なぜなら言語にはコンパイル時または実行時の「リフレクション」がないためです。モックライブラリがインターフェイスのメンバーを反復処理することはできません。
スレッドを参照してください: https://github.com/Microsoft/TypeScript/issues/1549
これは、依存関係のモックが開発ワークフローの中心部分であるTDD開発者にとっては残念なことです。
ただし、他の回答で説明されているように、メソッドをすばやくスタブ化するための多くの手法があります。これらのオプションは、少し精神的な調整を行って仕事をするかもしれません。
編集:TypeScript抽象構文ツリーASTは、コンパイル時の「イントロスペクション」です。これはおそらく、モックの生成に使用できます。しかし、実用的なライブラリを作成した人がいるかどうかはわかりません。
私はqUnitとSinonを使用してTypeScriptテストを書いてきましたが、あなたが説明しているのとまったく同じ痛みを経験しました。
次のようなインターフェースに依存していると仮定しましょう。
interface IDependency {
a(): void;
b(): boolean;
}
私は、sinonのスタブ/スパイとキャスティングに基づいたいくつかのアプローチを使用することにより、追加のツール/ライブラリの必要性を回避することができました。
空のオブジェクトリテラルを使用してから、コードで使用される関数にsinonスタブを直接割り当てます。
//Create empty literal as your IDependency (usually in the common "setup" method of the test file)
let anotherDependencyStub = <IDependency>{};
//Set stubs for every method used in your code
anotherDependencyStub.a = sandbox.stub(); //If not used, you won't need to define it here
anotherDependencyStub.b = sandbox.stub().returns(true); //Specific behavior for the test
//Exercise code and verify expectations
dependencyStub.a();
ok(anotherDependencyStub.b());
sinon.assert.calledOnce(<SinonStub>anotherDependencyStub.b);
コードに必要なメソッドの空の実装でオブジェクトリテラルを使用し、必要に応じてsinon spies/stubsでメソッドをラップします
//Create dummy interface implementation with only the methods used in your code (usually in the common "setup" method of the test file)
let dependencyStub = <IDependency>{
a: () => { }, //If not used, you won't need to define it here
b: () => { return false; }
};
//Set spies/stubs
let bStub = sandbox.stub(dependencyStub, "b").returns(true);
//Exercise code and verify expectations
dependencyStub.a();
ok(dependencyStub.b());
sinon.assert.calledOnce(bStub);
これらをsinonサンドボックスおよびqUnitモジュールによって提供されるような一般的なセットアップ/ティアダウンと組み合わせると、非常にうまく機能します。
次のようなものです(最初のオプションを使用しますが、2番目のオプションを使用している場合は同じように機能します)。
QUnit["module"]("fooModule", {
setup: () => {
sandbox = sinon.sandbox.create();
dependencyMock = <IDependency>{};
},
teardown: () => {
sandbox.restore();
}
});
test("My foo test", () => {
dependencyMock.b = sandbox.stub().returns(true);
var myCodeUnderTest = new Bar(dependencyMock);
var result = myCodeUnderTest.doSomething();
equal(result, 42, "Bar.doSomething returns 42 when IDependency.b returns true");
});
私はこれがまだ理想的な解決策ではないことに同意しますが、それは合理的にうまく機能し、追加のライブラリを必要とせず、必要な追加コードの量を低い管理可能なレベルに保ちます。
最新 TypeMoq (ver 1.0.2)は、ランタイム(nodejs/browser)がES6で導入されたProxyグローバルオブジェクトをサポートしている限り、モックTypeScriptインターフェイスをサポートします。
したがって、IDependency
は次のようになります。
interface IDependency {
a(): number;
b(): string;
}
typeMoqでモックするのは次のように簡単です:
import * as TypeMoq from "typemoq";
...
let mock = TypeMoq.Mock.ofType<IDependency>();
mock.setup(x => x.b()).returns(() => "Hello World");
expect(mock.object.a()).to.eq(undefined);
expect(mock.object.b()).to.eq("Hello World");
TypeMoq
、TeddyMocks
、およびTypeScript-mockify
は、おそらく最も人気のあるライブラリの1つです。
Githubリポジトリを確認し、好みのリポジトリを選択してください:リンク:
Sinonのようなより一般的なライブラリを使用することもできますが、最初に<any>
タイプを使用し、次に<IDependency>
タイプに絞り込む必要があります( TypescriptでSinonを使用するにはどうすればよいですか )
今では可能です。実行時にインターフェイスメタデータを利用できるようにするTypeScriptコンパイラの拡張バージョンをリリースしました。たとえば、次のように書くことができます。
interface Something {
}
interface SomethingElse {
id: number;
}
interface MyService {
simpleMethod(): void;
doSomething(p1: number): string;
doSomethingElse<T extends SomethingElse>(p1: Something): T;
}
function printMethods(interf: Interface) {
let fields = interf.members.filter(m => m.type.kind === 'function'); //exclude methods.
for(let field of fields) {
let method = <FunctionType>field.type;
console.log(`Method name: ${method.name}`);
for(let signature of method.signatures) {
//you can go really deeper here, see the api: reflection.d.ts
console.log(`\tSignature parameters: ${signature.parameters.length} - return type kind: ${signature.returns.kind}`);
if(signature.typeParameters) {
for(let typeParam of signature.typeParameters) {
console.log(`\tSignature type param: ${typeParam.name}`); //you can get constraints with typeParam.constraints
}
}
console.log('\t-----')
}
}
}
printMethods(MyService); //now can be used as a literal!!
これは出力です:
$ node main.js
Method name: simpleMethod
Signature parameters: 0 - return type kind: void
-----
Method name: doSomething
Signature parameters: 1 - return type kind: string
-----
Method name: doSomethingElse
Signature parameters: 1 - return type kind: parameter
Signature type param: T
-----
これらすべての情報を使用して、必要に応じてプログラムでスタブを構築できます。
私のプロジェクトを見つけることができます こちら 。
Ts-mockitoはバージョン2.4.0以降のモッキングインターフェイスもサポートしているようです: https://github.com/NagRock/ts-mockito/releases/tag/v2.4.
moq.ts を試すことができますが、Proxyオブジェクトに依存します
interface IDependency {
a(): number;
b(): string;
}
import {Mock, It, Times} from 'moq.ts';
const mock = new Mock<IDependency>()
.setup(instance => instance.a())
.returns(1);
mock.object().a(); //returns 1
mock.verify(instance => instance.a());//pass
mock.verify(instance => instance.b());//fail