【问题标题】:Sinon - How to stub a method called by the method I want to testSinon - 如何存根由我要测试的方法调用的方法
【发布时间】:2016-10-12 00:53:42
【问题描述】:

我在为要在 typescript 中测试的方法使用的方法存根时遇到问题。为了清楚起见,我在示例中删除了很多方法本身,但基本上我有一个调用getService 方法的getServiceWithRetry 方法。

即。

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

注意:对于那些不熟悉 bluebird 承诺的人,.then(function(value){}, function(error){}) 方法会处理承诺成功和承诺被拒绝时发生的情况。

【问题讨论】:

    标签: javascript typescript sinon


    【解决方案1】:

    你需要改变:

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

    【讨论】:

      【解决方案2】:

      问题在于,使用sinon.stub(Lookup, 'getService'),您正在改变您在测试中持有的 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 依赖的东西存根,而不是直接存根。这取决于您希望测试的粒度级别,但如果此代码很简单并且您可以进行更粗略的测试,这可能是一个简单的选择。

        即使您已经将它们分开,您也可能想要这样做,以获得额外覆盖的单元和集成测试。如果它们之间存在一些更复杂的交互,那就更是如此。

      • 1234563您将创建一个类,该类在其构造函数中获取依赖项(getService 方法),将其存储在内部,并在稍后在结果对象上调用方法时使用它。在您的生产代码中,其他东西必须正确地将它们粘合在一起,然后在您的测试中,您可以改为传递存根。
      • 对于我期望的这种情况也有点矫枉过正,但您可以将 getService 拉入 Lookup 导入的完全独立的模块中,并在测试期间使用 Rewire 之类的东西将其换成不同的模块。

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

      【讨论】:

        【解决方案3】:

        由于您使用的是 TypeScript,最好使用ts-mockito (npm 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');
        

        【讨论】:

          猜你喜欢
          • 2020-07-23
          • 2019-12-10
          • 2016-01-21
          • 2014-07-17
          • 2019-09-12
          • 2018-05-31
          • 2013-12-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多