annedroiid
annedroiid

Reputation: 6637

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.

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. 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. 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.

Upvotes: 2

Views: 5392

Answers (3)

robbiecahill_
robbiecahill_

Reputation: 135

Since your using TypeScript, it may be better to use ts-mockito (npm install --save ts-mockquito).

ts-mockito supports types.

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

Upvotes: 0

rabbitco
rabbitco

Reputation: 2850

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.

Upvotes: 3

Tim Perry
Tim Perry

Reputation: 13216

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. In your Lookup module though the function is just finding getService directly from its local scope. 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, e.g. so you can call it like retry(nTimes, getService, "serviceName") or retry(() => getService("serviceName"), nTimes)). If it's practical to do this (i.e. if it's not too tied it to getService) then you can then easily test this on its own:

    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)

  • Give up, and test them together. This means stubbing out the things that getService depends on, rather than stubbing it directly. 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. 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. 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 like Rewire to swap it out for a different module during testing.

    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.

Upvotes: 2

Related Questions