Sinon not stubbing on module.exports - node.js

If create an file with the following contents
const validateEmail = email => {
sendEmail(email);
};
const sendEmail = email => {
return true;
};
module.exports = {
validateEmail,
sendEmail,
};
And a test that tries to stub out the second function...
it('Should call sendEmail if a valid email is passed', () => {
let sendEmailSpy = sinon.stub(checkEmail, 'sendEmail');
checkEmail.validateEmail('acorrectemail#therightformat.com');
assert.isTrue(sendEmailSpy.called);
});
It still calls the sendEmail function and the test fails
However, if I write the module.exports like this:
module.exports = {
validateEmail(email) {
this.sendEmail(email);
},
sendEmail(email) {
return true;
},
};
It stubs it correctly...Why?

Short answer - context
Long answer - in the first scenario, the exported sendEmail function is not the same as the internal one that is used by validateEmail. The exported function becomes a new property of the object being exported and simply references the internal one.
In the second scenario, you explicitly reference the sendEmail function on the exported object (i.e. this.sendEmail(...)) from validateEmail therefore it will use the stubbed version.
Moral of the story - you can't stub something you can't see.

Related

Why can't I properly stub the twilio library with sinon?

In my code, I have:
function handleMessage() {
const twilio = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
let recordings = twilio.recordings(foundConference.RecordingSid);
console.log('recordings', recordings);
return recordings.remove();
}
And in my stub, I have:
const sinon = require('sinon');
const twilio = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
exports.twilioRecordings = () => {
console.log('about to stub', twilio.recordings);
sinon.stub(twilio, 'recordings').returns('here');
console.log('finished stub', twilio.recordings);
return;
};
However, it doesn't actually create a stubbed function. It's still using the original recordings function. What am I doing wrong?
Twilio npm package returns a function which creates a new object on every call, it's not a singleton. So your stubbed twilio instance is scoped to the test only.
Also twilio.recordings (as all other properties though) is defined through the getter function in prototype, so they are read only:
Object.defineProperty(Twilio.prototype,
'recordings', {
get: function() {
return this.api.account.recordings;
}
});
So, stubbing actual twilio instance have no effect. Except if you change the instance's prototype, but I don't think it worth doing for just unit testing.
I'd suggest you to refactor your code to put twilio initialization into separate method:
function getTwilio() {
return require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
}
Next your hangleMessage will look like:
function handleMessage() {
const twilio = this.getTwilio();
const recordings = twilio.recordings(...);
...
}
And next, in your test you can stub getTwilio() to return stub:
const twilioStub = {
recordings: sinon.stub(),
remove: sinon.stub()
}
sinon.stub(myAwesomeModule, 'getTwilio').returns(twilioStub);
You also can consider using mock-require package:
const sinon = require('sinon');
const mock = require('mock-require');
mock('twilio', () => ({
recordings: sinon.stub(),
}));
Here is a question about how-to mock dependencies, there might be other helpful libraries to stub required module.
Hope it helps.

Mocha spies returns callCount 0

I am using mocha, chai and sinon for unit testing in node env. I need to test a scenario where it makes a call to services to fetch the data and return the data.
My controller looks like this:
{
get model() { return schema},
async findUser(data) {
const data = await this.model.find({ id: data.id });
return data;
}
}
In my mocha test I am using the Sinon stub to return the model and find function some like this:
sinon.stub(controller, 'model').get(() => ({
find: () => ({ username: 'asdf' })
}));
My test is working as expected. Now I want to test see if my find method id called once and the arguments passed to it. To do that, i added following code
const spyFind = sinon.spy(controller.model, 'find');
assert.isTrue(spyFind.calledOnce);
This should return true because the spyFind is called and it returned the expected mock value. But when i debug, the spyFind object says isCalled 'false'. Can someone help me understand what am i doing wrong?
The way your stub is designed it is going to return a new object every time controller.model is called. As result:
controller.model === controller.model // false
So when you try to spy on the find property of controller.model with:
const spyFind = sinon.spy(controller.model, 'find');
Sinon grabs the object returned by controller.model and stubs find on that object. The the next time you call controller.model, for example in your test, you will get a fresh object from controller.model that is not spied on. So the original spy is never called.
I think better approach is to provide a singe stub that is returned by find(), then you can tell if the stub is called:
const sinon = require('sinon')
let controller = {
get model() { return schema},
async findUser(data) {
const data = await this.model.find({ id: data.id });
return data;
}
}
let findStub = sinon.stub()
findStub.returns({ username: 'asdf' })
sinon.stub(controller, 'model').get(() => ({
find: findStub
}));
console.log(controller.model.find()) // { username: 'asdf' }
console.log(findStub.calledOnce); // true

Mocha, Sinon and Chai testing two http calls inside a callback

I am doing some really simple testing with Chai Mocha and Sinon. I am wondering how you would go about testing a http method that gets called inside of a callback. Please point me in the right direction if you can, struggling to find anything on it, I know you can do it, just not sure how. My code is below:
index.js
const http = require('http')
class Index {
add(a, b) {
return a + b
}
get(uri) {
http.get(uri, () => {
http.get('/', function() {
return
})
})
}
}
module.exports = Index
index.spec.js
const Index = require('../index')
const http = require('http')
const { stub, fake, assert } = require('sinon')
const { expect } = require('chai')
let httpSpy;
beforeEach(function () {
a = new Index()
httpSpy = stub(http, 'get')
})
describe('When the get method is invoked', function () {
beforeEach(function () {
a.get('http://www.google.co.uk')
})
it('should make a call to the http service with passed in uri', function () {
assert.calledOnce(httpSpy) // This really should be called twice (Part I am struggling with)
assert.calledWith(httpSpy, 'http://www.google.co.uk')
// I want to test instead that the httpSpy was called twice as, inside the get method, when the first http get resolves, another one gets fired off also
})
})
There are two issues.
Firstly, we cannot tell when the Index.get() method execution is ended (it does not accept a callback, neither return a promise, not marked as async etc).
get(uri) { ... }
Usage of such method is critically inconvenient. For example: if we'd want to first do Index.get() and then do some action right after we won't be able to.
To fix the this we can just add a callback as a last parameter of Index.get.
The second issue is how the http.get method is stubbed:
httpSpy = stub(http, 'get')
This line basically means: replace http.get with an empty function. That dummy function won't throw if you pass a callback inside but it won't call it.
That is why http.get is called only once. It simply ignores the passed callback where http.get should be called the second time.
To fix this we can use stub.yields() method to make sinon aware that the last parameter passed to a stub is a callback (and sinon needs to call it). You can find the method in the docs.
Here is a working example, please see my comments:
class Index {
// Added a callback here
get(uri, callback) {
http.get(uri, () => {
http.get('/', () => {
// Pass any data you want to return here
callback(null, {});
})
})
}
}
let httpSpy;
beforeEach(() => {
a = new Index()
// Now sinon will expect a callback as a last parameter and will call it
httpSpy = stub(http, 'get').yields();
})
describe('When the get method is invoked', () => {
const uri = 'http://www.google.co.uk';
// Now we are waiting for the execution to end before any assertions
beforeEach(done => {
a.get(uri, done);
});
it('should make a call to the http service with passed in uri', () => {
assert.calledTwice(httpSpy);
assert.match(httpSpy.getCall(0).args[0], uri);
assert.match(httpSpy.getCall(1).args[0], '/');
});
})

Sinon spy calledWithNew not working

I'm trying to use sinon or sinon-chai's calledWithNew (or simply called), but can't seem to get it to work, I've looked at a few suggestions online without luck either, here is the function I'm trying to test:
users.js
exports.create = function (data) {
//some validation
var user = new User(data);
return user.save().then((result) => {
return mailer.sendWelcomeEmail(data.email, data.name).then(() => {
return {
message: 'User created',
userId: result.id
};
});
}).catch((err) => {
return Promise.reject(err);
});
}
Here is my test:
users.test.js
beforeEach(() => {
saveStub = sandbox.stub(User.prototype, 'save').resolves(sampleUser);
spy = sandbox.spy(User);
});
afterEach(() => {
sandbox.restore();
});
it('should call user.save', async () => {
result = await users.create(sampleArgs);
expect(saveStub).to.have.been.called; //-> true
expect(spy).to.have.been.called; //-> false, calledWithNew returns same result as well
});
I found several posts suggesting spying on (window, 'className') but I'm using mocha, not a browser.
Trying to spy on (global, User / User.prototype) didn't work either.
User is a module-level variable in users.js. Sinon cannot affect it. When you do this in your test file:
spy = sandbox.spy(User);
You're creating a spy in the scope of your test file, sure, but your module is still using the original.
To do something like this, you need to export your constructor inside an object, then both invoke it and spy on it through that object:
Wherever User is coming from, let's say user.js:
class User {
// whatever your User implementation is
}
module.exports = { User };
users.js:
const userModule = require('./user');
...
var user = new userModule.User(data);
Then, in your test file:
const userModule = require('./user');
spy = sandbox.spy(userModule, 'User');
Another way to do this would be to use something like proxyquire. It can make these kinds of tests less obtrusive, but can make your tests more confusing to readers.
My preference is generally to keep constructors very simple so I don't ever have to spy on them. I've never used calledWithNew in any of my own projects for this reason. :\ It's up to you, though.

How can I mock a function/property of a node module exported as a function using sinon?

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

Resources