Why can't I properly stub the twilio library with sinon? - node.js

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.

Related

Why is my sinon stub not working and trying to make a external call?

Hi I am working on making a unit test for AWS lambda written in JS.
To be honest, it is my first time writing a test.
I decided to use chai, mocha, and sinon libraries.
Here is my actual code
// index.js
const AWS = require("aws-sdk");
const getParam = async (path) => {
const ssm = new AWS.SSM();
const params = {
Name: path,
WithDecryption: false
}
const result = await ssm.getParameter(param).promise();
const value = result['Parameter']['Value']
console.log("##", value);
return value;
}
And here is what I got so far by reading other posts and documentation codes.
// index.test.js
const AWS = require("aws-sdk");
AWS.config.update({ region: 'us-east-1' });
const chai = require('chai');
const expect = chai.expect;
const sinon = require("sinon");
const { getParam } = require('./index.js');
describe("test1", () => {
if('testing', async () => {
const ssm = AWS.SSM();
sinon.stub(ssm, "getParameter").withArgs("testing")
.resolve({ Parameter: { Value: "TESTING VALUE FROM PARAM STORE" } });
const res = await getParam("Hello");
console.log(res);
expect("TESTING VALUE FROM PARAM STORE").to.equal("TESTING VALUE FROM PARAM STORE");
})
})
When I ran the test, it asked for AWS secrets, which made me realize that it was not how I expected this to behave.
If it works correctly, it will not bother connecting to AWS at all, I believe.
And it should call getParam function and return the value from resolve function.
May I know what I am missing?
Or am I misusing stub function?
I read somewhere that stub function is used when we need to see what happened during the test like how many times a certain function was called and etc...
However, I saw some of the posts using the stub function to do a similar thing that I am doing.
Thank you in advance.
You're creating an instance of SSM inside your test and stubbing that, not the instance of SSM that lives inside your getParam method.
Instead, what you can do is stub against the prototype so that all instances from thereon will use stub when invoked.
sinon.stub(AWS.SSM.prototype, "getParameter")
.withArgs("testing")
.callsFake(() => ({
promise: Promise.resolve({
Parameter: {
Value: "TESTING VALUE FROM PARAM STORE"
}
})
}));
You can't use .resolves() on get Parameter too as it doesn't return a promise, that's what you're .promise() chained method is responsible for, so we have to replicate this :).

How to mock the return value of messages.create() method from twilio-node using sinon js/loopback testlab?

I'm trying to mock the return value of messages.create() method from twilio-node library.
Since the create method resides inside the interface called messages, i can't directly mock the return value of create method.
My Unit test:
import {
createStubInstance,
StubbedInstanceWithSinonAccessor,
} from '#loopback/testlab';
import sinon from 'sinon';
import {Twilio} from '../../../../clients/whatsapp-sms-clients/twilio.whatsapp-sms-clients';
import twilio from 'twilio';
describe('Twilio client (UnitTest)', () => {
let twilioMock: StubbedInstanceWithSinonAccessor<twilio.Twilio>;
let logger: StubbedInstanceWithSinonAccessor<LoggingService>;
let twilioClient: Twilio;
beforeEach(() => {
twilioMock = createStubInstance(twilio.Twilio);
logger = createStubInstance(LoggingService);
twilioClient = new Twilio(twilioMock, logger);
});
it('should create the message', async () => {
twilioMock.stubs.messages.create.resolves({
// mocked value
});
});
});
Thanks in advance.
Twilio developer evangelist here.
I've not worked with testlab/sinon like this before, but I think I have an idea of what you need to do, if not the right syntax.
You'd need to stub the response to twilioMock.messages to return an object that has a create property that is a stubbed function that resolves to the result you want. Something like this might work, or at least set you on the right track:
it('should create the message', async () => {
// Create stub for response to create method:
const createStub = sinon.stub().resolves({
// mocked value
});
// Stub the value "messages" to return an object that has a create property with the above stub:
twilioMock.stubs.messages.value({
create: createStub
});
// Rest of the test script
});
Edit
OK, using value above didn't work. I tried again. This version strips out your custom Twilio wrapper from the example and just calls things directly on the Twilio client stub itself. Hopefully you can use this as inspiration to work it into your tests.
What I realised is that twilioClient.messages is a getter and is dynamically defined. So, I directly stubbed the result on the stub client.
import {
createStubInstance,
StubbedInstanceWithSinonAccessor,
} from "#loopback/testlab";
import sinon from "sinon";
import { Twilio } from "twilio";
describe("Twilio client (UnitTest)", () => {
let twilioMock: StubbedInstanceWithSinonAccessor<Twilio>;
beforeEach(() => {
twilioMock = createStubInstance(Twilio);
});
it("should create the message", async () => {
const createStub = sinon.stub().resolves({
sid: "SM1234567",
});
sinon.stub(twilioMock, "messages").get(() => ({
create: createStub,
}));
const message = await twilioMock.messages.create({
to: "blah",
from: "blah",
body: "hello",
});
expect(message.sid).toEqual("SM1234567");
});
});
The above test passes for me in my setup.

How to spy/stub/mock firestore db.collection().add() when db is a custom class property, i.e. how to stub this.db.collection().add()?

I have a class, Firestore, that inits a firebase db in the constructor, this.db, and has an addEntry() method that adds a new entry to the db. How can I stub/mock the write to the db so that no writes are made during testing? The assertion of this test is that db.collection().add() is called once.
firestore.js
class Firestore {
constructor() {
this.db = firestoreAdmin.firestore()
}
async addEntry(newEntry) {
newEntry.claimed = "false"
var collectionReference = await this.db.collection('collection_name').add(newEntry)
return collectionReference._path.segments[1]
}
}
test_firestore.js
const sinon = require('sinon')
const chai = require('chai')
const Firestore = require('../firestore.js')
describe('file firestore.js | class Firestore', function() {
const firestore = new Firestore()
describe('method addEntry(newEntry)', function() {
it('should call this.db.collection().add() once', function() {
var newEntry = {
"client": "clientName"
}
var add = sinon.spy(firestore.db.collection, 'add')
firestore.addEntry(newEntry)
sinon.assert.calledOnce(add)
add.restore()
})
})
})
Right now I'm getting this error:
1 failing
1) file firestore.js | class Firestore
method addEntry(newEntry)
should add key:value pair (claimed: false) prior to writing to db:
TypeError: Attempted to wrap undefined property add as function
Instead of spy sinon doc, consider use stub sinon doc. A spy will wrap the original function and does exactly what the original function does, in your case, write to the database.
Meanwhile, a stub should be used when you want to prevent a specific method from being called directly.
var add = sinon.stub(firestore.db.collection, 'add')
With the comments below, it looks like you are trying to stub a complex object, in this case, you can actually assign a new value to the property without any sinon methods like this:
const fakeAdd = sinon.fake()
firestore.db.collection = [
{add: fakeAdd}
]
firestore.addEntry();
sinon.assert.calledOnce(fakeAdd)
And for async method unit testing, you can simply mark the test method as async as well.
it('should do something', async () => {
await firestore.addEntry()
})
A working codepen example:
https://codepen.io/atwayne/pen/VweOXpQ

Testing functions with properties using Jest and Sinon

am trying to write tests to test streams on my app, dealing with fs library with its createWriteStream function, so the stub i created is as follows:
writeStreamStub = sinon.stub()
onceStreamEventStub = sinon.stub()
endStreamStub = sinon.stub()
onStreamStub = sinon.stub()
createStreamStub = sinon.stub(fs, 'createWriteStream').returns({
write: writeStreamStub,
once: onceStreamEventStub,
end: endStreamStub,
on: onStreamStub
})
So now I can test for whether the functions are called and the returned functions are also called. But I am using the --coverage flag and the code of the callbacks of the returned functions is not covered, the write method is called inside a process.nextTick and I have no idea how to go about this. Is it possible to cover the whole code and the code inside the callbacks, and if so, how do I go about it. Thanks in advance.
N.B. The variables are globaly declared
If there's no cogent reason to use both sinon and jest, I'd recommend just using one library. If you decide to go with jest, here's a simple example. Assume you have a class like
const fs = require('fs');
module.exports = class FileWriter {
constructor() {
this.writer = fs.createWriteStream('./testfile.txt');
}
writeFile() {
process.nextTick(() => {
this.writeContent('hello world');
});
}
writeContent(content) {
this.writer.write(content);
this.writer.end();
}
};
and in your unit-test you want to mock the behaviour of all the used fs-functions (createWriteStream, writer, end in this case) and just check if they are called with the correct arguments. You could do this with something like this:
const fs = require('fs');
const FileWriter = require('./FileWriter');
// use this to have mocks for all of fs' functions (you could use jest.fn() instead as well)
jest.mock('fs');
describe('FileWriter', () => {
it('should write file with correct args', async () => {
const writeStub = jest.fn().mockReturnValue(true);
const endStub = jest.fn().mockReturnValue(true);
const writeStreamStub = fs.createWriteStream.mockReturnValue({
write: writeStub,
end: endStub,
});
const fileWriter = new FileWriter();
fileWriter.writeFile();
await waitForNextTick();
expect(writeStreamStub).toBeCalledWith('./testfile.txt');
expect(writeStub).toBeCalledWith('hello world');
expect(endStub).toHaveBeenCalled();
});
});
function waitForNextTick() {
return new Promise(resolve => process.nextTick(resolve));
}

Sinon.js and testing with events and new

i'm trying to mock a node.js application, but it doesn't work as expected.
I have a node.js Module called GpioPlugin with following method:
function listenEvents(eventId, opts) {
if(!opts.pin) {
throw new Error("option 'pin' is missing");
}
var listenPort = new onOff(opts.pin, 'in', 'both', {persistentWatch: true});
listenPort.watch(function(err, value) {
process.emit(eventId+'', value);
});
}
if(typeof exports !== 'undefined') {
exports.listenEvents = listenEvents;
}
and now i want to write a test using sinon for this method, but i don't know how... What would be the best way to test this?
This tree parts would be fine, if they get tested:
Error (no problem)
generation of onOff (how?)
event with correct params
If it's not already, you're going to want to put onOff into a module so that your test can inject a stub in.
var sinon = require("sinon");
var process = require("process");
var onOffModule = require(PATH_TO_ONOFF); //See note
var gpio = require(PATH_TO_GPIO);
var onOffStub;
var fakeListenPort;
beforeEach(function () {
//Stub the process.emit method
sinon.stub(process, "emit");
//Constructor for a mock object to be returned by calls to our stubbed onOff function
fakeListenPort = {
this.watch = function(callback) {
this.callback = callback; //Expose the callback passed into the watch function
};
};
//Create stub for our onOff;
onOffStub = sinon.stub(onOffModule, "onOff", function () {
return fakeListenPort;
});
});
//Omitted restoring the stubs after each test
describe('the GpioPlugin module', function () {
it('example test', function () {
gpio.listenEvents("evtId", OPTS);
assert(onOffStub.callCount === 1); //Make sure the stub was called
//You can check that it was called with proper arguments here
fakeListenPort.callback(null, "Value"); //Trigger the callback passed to listenPort.watch
assert(process.emit.calledWith("evtId", "Value")); //Check that process.emit was called with the right values
});
});
Note: The exact mechanics of replacing onOff with a stub may vary depending how you require it.
Things get a little more complicated if you require onOff directly, rather than requiring a module that includes onOff. In that case I think you might need to look into something like proxyquire.

Resources