2016-10-12 5 views
0

typescriptでテストしたいメソッドで使用されているメソッドをスタブするのに問題があります。この例では、わかりやすくするために多くのメソッド自体を取り除いていますが、基本的には、getServiceメソッドを呼び出すgetServiceWithRetryメソッドがあります。Sinon - 私がテストしたいメソッドで呼び出されたメソッドをスタブする方法

ie。

export function getServiceWithRetry(name:string, triesLeft:number) { 
    //do stuff 
    getService(name) 
    //do more stuff 
} 

export function getService(name:string) { 
    //lookup stuff 
} 

これはLookupとしてテストにインポートされます。テストでgetServiceを呼び出すのであれば、私は正常にgetServiceメソッドをスタブすることができますが、getServiceWithRetryを実行すると実際のgetServiceメソッドとスタブが呼び出されません。誰かが私が間違っていることを知っていますか?

it("test", function(done) { 
    let serviceStub = sinon.stub(Lookup, 'getService') 

    serviceStub.returns(Promise.resolve("resolved")) 

    //this uses the stub 
    Lookup.getService("name").then(function(value) { 
     console.log("success: "+value) 
    }, function(error) { 
     console.log("error: "+error) 
    }) 

    //this calls the actual method, not the stub as I would expect it to 
    Lookup.getServiceWithRetry("serviceName", 4).then(function(value) { 
     console.log("success: "+value) 
    }, function(error) { 
     console.log("error: "+error) 
    }) 
    done() 
}) 

注:ブルーバードの約束に慣れていない人のために.then(function(value){}, function(error){})方法は、約束が成功した場合との約束が拒否された場合に何が起こるかを処理します。

答えて

2

問題は、sinon.stub(Lookup, 'getService')では、テストで保持しているLookup変数の内部を変更し、その変数からメソッドを取得しているという問題があります。ルックアップモジュールでは、機能はローカルスコープから直接getServiceを見つけるだけです。外部的には、あなたがその範囲で混乱する可能性があるとは思わないので、私は恐怖を感じることはありません。

一般に、テストでは通常、1つのモジュールの部品をうまく模擬することができません。これを少し再構成する必要があります。いくつかのオプションがあります:

  • 完全に別々にテストしてください。 getServiceWithRetryを一般的なretryメソッドに変更します。したがって、retry(nTimes, getService, "serviceName")またはretry(() => getService("serviceName"), nTimes)のように呼び出すことができます)。 (それはあまりにもgetServiceにそれを縛られていない場合、すなわち)それはこれを行うには実用的だなら、あなたは、簡単に自分自身でこれをテストすることができます。

    var myStub = sinon.stub(); 
    
    myStub.onCall(0).throw("fail once"); 
    myStub.onCall(0).throw("fail twice"); 
    myStub.returns(true); // then return happily 
    
    expect(retry(myStub, 1)).to.throw("fail twice"); // gives up after one retry 
    expect(retry(myStub, 5)).to.return(true); // keeps going to success 
    

    あなただけの単一getServiceWithRetryを呼び出すことができるようにしたい他の場所ならば、あなたを簡単に構築できます:var getServiceWithRetry = (arg, triesLeft) => retry(getService, tries)

  • あきらめて一緒にテストしてください。これは、直接スタブするのではなく、getServiceが依存するものをスタブアウトすることを意味します。テストでどのレベルの細かさが必要かによって異なりますが、このコードが単純でより粗くテストできる場合は、これは簡単なオプションです。

    とにかく別にしても、余分なカバレッジのユニットテストと統合テストを受けることができます。これらの間にさらに複雑なやり取りがある場合、これは二重に当てはまります。

  • このケースでは、私が見ているものとは関係ないかもしれませんが、他のケースでは、テスト中のメソッド(getServiceWithRetry)をクラスに入れて、依存性注入を使用するようなものです。依存関係(getServiceメソッド)をそのコンストラクタで受け取り、内部的に格納し、結果オブジェクトのメソッドを呼び出すときに後で使用するクラスを作成します。あなたのプロダクションコードでは、何か他のものを正しく連結しなければならず、テストでは代わりにスタブを渡すことができます。

  • また、私は期待していますが、getServiceをLookupのインポートとは全く別のモジュールに入れて、Rewireのようなものをテスト中に別のモジュールに置き換えることができます。

    これは依存関係注入オプションと非常によく似ていて、生産コードを簡単にしますが、テストコードを複雑で魔法にする代償です。

    export function getServiceWithRetry(name:string, triesLeft:number) { 
        //do stuff 
        getService(name) 
        //do more stuff 
    } 
    

    へ:

2

あなたが変更する必要がgetService()通話がモジュールに存在するLookup.getService()ではなくgetService()を指します

export function getServiceWithRetry(name:string, triesLeft:number) { 
    //do stuff 
    this.getService(name) 
    //do more stuff 
} 

あなたがLookup.getServiceWithRetry()を呼んでそのように、あなたのからエクスポートしています。

0

TypeScriptを使用しているので、ts-mockitonpm install --save ts-mockquito)を使用する方がよい場合があります。

ts-mockitoがサポートされています。

次に、(わずかに変更され、READMEから)のようなあなたのクラスをモックすることができます

// Creating mock 
let mockedFoo:Foo = mock(Foo); 

// Getting instance from mock 
let foo:Foo = instance(mockedFoo); 

// Using instance in source code 
foo.getBar(3); 
foo.getBar(5); 

// Explicit, readable verification 
verify(mockedFoo.getBar(3)).called(); 
verify(mockedFoo.getBar(5)).called(); 
when(mockedFoo.getBar(4)).thenReturn('three'); 
関連する問題