简体   繁体   English

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

[英]Sinon - How to stub a method called by the method I want to test

I'm having trouble stubbing a method that is used by a method I want to test in typescript.我在存根要在打字稿中测试的方法使用的方法时遇到问题。 I've stripped out a lot of the method itself in the example for clarity but basically I have a getServiceWithRetry method that calls the getService method.我已经剥离出了很多方法本身为了清楚的例子,但基本上我有一个getServiceWithRetry调用该方法getService方法。

ie.即。

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

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

This is imported into my test as Lookup .这作为Lookup导入到我的测试中。 I can successfully stub out the getService method if I'm calling getService in the test, however when I run getServiceWithRetry it calls the actual getService method and not the stub.如果我在测试中调用getService ,我可以成功地存根 getService 方法,但是当我运行getServiceWithRetry它调用实​​际的getService方法而不是存根。 Does anyone know what I'm doing wrong?有谁知道我做错了什么?

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()
})

Note: For those unfamiliar with bluebird promises the .then(function(value){}, function(error){}) method handles what happens if the promise is successful and if the promise is rejected.注意:对于那些不熟悉 bluebird Promise 的人.then(function(value){}, function(error){})方法会处理 Promise 成功和 Promise 被拒绝时发生的情况。

You need to change:你需要改变:

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

to:到:

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

that way when you call Lookup.getServiceWithRetry() the getService() call will point to Lookup.getService() rather than the getService() residing in the module your are exporting from.这样,当您调用Lookup.getServiceWithRetry()getService()调用将指向Lookup.getService()而不是驻留在您从中导出的模块中的getService()

The problem is that with sinon.stub(Lookup, 'getService') you're mutating the insides of the Lookup variable you're holding in your tests, and then getting the method from that variable.问题在于,使用sinon.stub(Lookup, 'getService')您正在改变测试中保存的 Lookup 变量的内部,然后从该变量中获取方法。 In your Lookup module though the function is just finding getService directly from its local scope.在您的 Lookup 模块中,该函数只是直接从其本地范围内查找getService Externally I don't think there's any way you can mess with that scope, so there's no easy magic fix for this I'm afraid.从外部看,我认为您没有任何办法可以弄乱该范围,因此恐怕没有简单的魔法解决方法。

Generally, you usually can't nicely mock parts of a single module in a test.通常,您通常无法在测试中很好地模拟单个模块的部分。 You need to restructure this a bit, and there's a few options:您需要对其进行一些重组,有几个选项:

  • Test them totally separately.完全分开测试它们。 Change getServiceWithRetry into a generic retry method, eg so you can call it like retry(nTimes, getService, "serviceName") or retry(() => getService("serviceName"), nTimes) ).将 getServiceWithRetry 更改为通用retry方法,例如,您可以像retry(nTimes, getService, "serviceName")retry(() => getService("serviceName"), nTimes)一样调用它。 If it's practical to do this (ie if it's not too tied it to getService ) then you can then easily test this on its own:如果这样做可行(即,如果它与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

    If elsewhere you want to be able to just call a single getServiceWithRetry, you can build one easily: var getServiceWithRetry = (arg, triesLeft) => retry(getService, tries)如果在其他地方您希望能够只调用一个 getServiceWithRetry,您可以轻松构建一个: var getServiceWithRetry = (arg, triesLeft) => retry(getService, tries)

  • Give up, and test them together.放弃,一起测试。 This means stubbing out the things that getService depends on, rather than stubbing it directly.这意味着剔除getService依赖的东西,而不是直接剔除它。 It depends on what level of granularity you want from your tests, but if this code is simple and you can test more coarsely, this might be an easy option.这取决于您希望测试的粒度级别,但如果此代码很简单并且您可以进行更粗略的测试,那么这可能是一个简单的选择。

    You might want to do this even if you've separately them anyway, to get a unit and integration test for extra coverage.即使您已经将它们分开,您也可能想要这样做,以获得额外覆盖的单元和集成测试。 This is doubly true if there is some more complicated interactions going on between them.如果它们之间存在一些更复杂的交互,则更是如此。

  • Maybe not relevant in what this case, from what I can see, but in other cases sort-of like put the method-under-test (getServiceWithRetry) in a class, and use dependency injection.从我所见,可能与这种情况无关,但在其他情况下,有点像将被测方法(getServiceWithRetry)放在一个类中,并使用依赖注入。 You'd create a class that takes the dependency (the getService method) in its constructor, stores it internally, and uses it later when you call methods on resulting object.您将创建一个类,该类在其构造函数中接受依赖项(getService 方法),将其存储在内部,并在稍后在结果对象上调用方法时使用它。 In your production code something else will have to glue those together correctly, and then in your tests you can pass in a stub instead.在你的生产代码中,其他东西必须正确地将它们粘合在一起,然后在你的测试中你可以传入一个存根。

  • Also overkill for this case I expect, but you could pull getService into a totally separate module that Lookup imports, and use something likeRewire to swap it out for a different module during testing.对于我期望的这种情况,也有点矫枉过正,但是您可以将getService拉入 Lookup 导入的完全独立的模块中,并在测试期间使用Rewire 之类的东西将其替换为不同的模块。

    This is quite similar to the dependency injection option really, and makes your production code simpler, but at the cost of making your testing code more complicated and magical.这与依赖注入选项非常相似,并且使您的生产代码更简单,但代价是使您的测试代码更加复杂和神奇。

Since your using TypeScript, it may be better to use ts-mockito ( npm install --save ts-mockquito ).由于您使用的是 TypeScript,因此最好使用ts-mockito ( npm install --save ts-mockquito )。

ts-mockito supports types. ts-mockito支持类型。

You can then mock your classes like (from the README, slightly modified):然后你可以模拟你的类(来自自述文件,稍作修改):

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM