Reputation: 145
I have a service module that is exported as a function. I need to pass a couple of things into it, like a configuration object so it does need to retain this structure. I am trying to stub out a function from the service but can't figure it out. In my app, I have a function that makes an API call that is problematic during testing so I'd like to stub it. (I understand I'd have to write my test differently to handle the async issue)
// myService.js
module.exports = function(config) {
function foo() {
returns 'bar';
}
return {
foo: foo
};
};
// test.js
var config = require('../../config');
var request = require('supertest');
var chai = require('chai');
var expect = chai.expect;
var sinon = require('sinon');
var myService = require('./myService.js')(config);
describe('Simple test', function(done) {
it('should expect "something else", function(done) {
var stub = sinon.stub(myService, 'foo').returns('something else');
request(server) // this object is passed into my test. I'm using Express
.get('/testRoute')
.expect(200)
.expect(function(res) {
expect(res.body).to.equal('something else');
stub.restore();
})
.end(done);
});
});
* /testRoute I set up as a simple GET route that simply returns the value from myService.foo()
The above is not working, and I believe it has to do with the way my service is exporting. If I write the service as below, the stub works fine.
module.exports = {
test: function() {
return 'something';
}
};
But again, I need to be able to pass in information to the module so I would like to keep my modules in the original structure above. Is there a way to stub a function from a module that exports in that manner? I was also looking into proxyquire but not sure if that is the answer.
Upvotes: 1
Views: 2124
Reputation: 4586
The reason why your test stub does not work is that the foo
function is created every time the module initializer is called. As you discovered, when you have a static method on the module, then you are able to stub.
There are a variety of solutions to this problem-- but the simplest is to expose the method statically.
// myService.js
module.exports = function(config) {
return {
foo: foo
};
};
var foo = module.exports.foo = function foo() {
return 'bar'
}
It's ugly, but works.
What if the foo function has a closure to variables within the service (which is why it lives within the service initializer). Then unfortunately these need to be explicitly passed in.
// myService.js
module.exports = function(config) {
return {
foo: foo
};
};
var foo = module.exports.foo = function(config) {
return function foo() {
return config.bar;
}
}
Now you can safely stub the module.
However, how you are stubbing should be considered unsafe. Only if your test works perfectly does the stub get cleaned up. You should always stub within the before
and after
(or beforeEach
and afterEach
) fixtures, such as:
// We are not configuring the module, so the call with config is not needed
var myService = require('./myService.js');
describe('Simple test', function(done) {
beforeEach(function () {
// First example, above
this.myStub = sinon.stub(myService, foo).returns('something else');
// Second example, above
this.myStub = sinon.stub(myService, foo).returns(function () {
returns 'something else';
});
});
afterEach(function () {
this.myStub.restore();
});
it('should expect "something else", function(done) {
request(server) // this object is passed into my test. I'm using Express
.get('/testRoute')
.expect(200)
.expect(function(res) {
expect(res.body).to.equal('something else');
})
.end(done);
});
});
There are other options to be able to stub dependencies using dependency injection. I recommend you look at https://github.com/vkarpov15/wagner-core or my own https://github.com/CaptEmulation/service-builder
Upvotes: 3