Sinon - 如何存根我想要测试的方法调用的方法

时间:2016-10-12 00:53:42

标签: javascript typescript sinon

我无法对我想要在打字稿中测试的方法使用的方法进行存根处理。为了清晰起见,我在示例中删除了很多方法本身,但基本上我有getServiceWithRetry方法调用getService方法。

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){})方法会处理如果承诺成功并且承诺被拒绝会发生什么。

3 个答案:

答案 0 :(得分:3)

您需要更改:

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

为:

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

当您拨打Lookup.getServiceWithRetry() getService()来电时,Lookup.getService()将会指向getService(),而不是您所导出的模块中的apple dog orange

答案 1 :(得分:2)

问题在于,sinon.stub(Lookup, 'getService')您正在改变您在测试中保留的Lookup变量的内部,然后从该变量中获取方法。在Lookup模块中,虽然该函数只是直接从其本地范围查找getService。在外部,我不会认为你有任何方式可以搞砸这个范围,所以我不敢轻易修复这个问题。

通常,您通常无法在测试中模拟单个模块的某些部分。你需要重新调整一下,并且有几个选项:

  • 完全单独测试它们。将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之类的内容将其交换为其他模块测试

    这与依赖注入选项非常相似,并且使您的生产代码更简单,但代价是使您的测试代码更加复杂和神奇。

答案 2 :(得分:0)

由于您使用的是TypeScript,因此最好使用ts-mockitonpm install --save ts-mockquito)。

ts-mockito支持类型。

然后你可以模拟你的类(来自自述文件,稍加修改):

// 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');