Reputation: 6637
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
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
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
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