How to stub an object method with sinon? - node.js

I need to stub the sendMandrill method of the mh object.
See my file under test (mail.js):
let MailHandler = require('../../modules/mail.handler.module');
...
let api = (router, parser) => {
let send = async (req, res, next) => {
let mh = new MailHandler();
mh.sendMandrill();
...
}
...
return router.post('/mail/send', parser.json(), send);
}
module.exports = api;
...
My test (mail.spec.js):
let stRequest = require('supertest');
let MailHandler = require('../../modules/mail.handler.module');
describe('my test', () => {
beforeEach(() => {
sinon.stub(MailHandler.prototype, 'sendMandrill', () => true);
})
it('stubs sendMandrill!', done => {
stRequest(app)
.post('/mail/send')
.end((err, resp) => {
done();
});
})
})
Currently I'me getting the error below:
TypeError: Cannot stub non-existent own property sendMandrill
Adding mail.handler.module - See below the mailHandler / sendMandrill code:
module.exports = mailHandler;
function mailHandler() {
...
var mandrill = require('../modules/mandrill');
var handler = {
sendMandrill: sendMandrill,
...
};
return handler;
function sendMandrill() {
mandrill.messages.sendTemplate({
message: {...}
});
}
...
}

You current approach creates a new sendMandrill for each and every instance created by mailHandler factory. You should actually call it w/o new let mh = mailHandler() or even better rename it to createMailHandler to avoid misuse.
If you want to effectively use prototype inheritance you'll need to rewrite mailHandler to use actually use this instead of a newly created object.
var mandrill = require('../modules/mandrill');
module.exports = MailHandler;
function MailHandler() {
// use this instead of newly created object
this.foo = 'bar'
// avoid explicit return
// return handler;
}
// set methods to prototype
MailHandler.prototype.sendMandrill = function sendMandrill() {
// use this instead of handler here
mandrill.messages.sendTemplate({
message: {...}
});
}
Using the above approach you would be able to stub prototype properties via sinon and justify calling the constructor with new keyword.
UPD
If you have no control over mail.handler.module you could either use rewire module that allows to mock entire dependencies or expose MailHandler as a part of your api module to make it injectable.
api.MailHandler = require('../../modules/mail.handler.module')
let mh = api.MailHandler();
And then in tests
let oldMailHandler;
beforeAll(() => { oldMailHandler = api.MailHandler})
afterAll(() => { api.MailHandler = oldMailHandler})
beforeEach(() => { api.MailHandler = function MockMailHandler() {} })

Related

Not able to cover multiple model function in jest test node js

I am new in node and jest unit testing. I have a method in controller where i am calling multiple model function and i want to cover all below query related to that particular method.
**Control code**
`interviewTypes = await InterviewType.findAll({});
interviewMediums = await InterviewMedium.findAll({});
toBeSortedinterviewDurations = await InterviewDuration.findAll({});`
test file
`it("Test getAllInterviewSlotDetails for branch sequelize", async () => {
let thrownError;
let param = {
sequelize: true
}
let getModelMock = getModels.mockImplementation(async () => {
return {};
});
let getMock = new getModelMock(sequelize);
try {
await interviews.getAllInterviewSlotDetails(param);
} catch (e) {
thrownError = e;
}
});`

How can I mock this http request using jest?

I am new to using Jest for unit tests. How can I mock this simple http request method "getData"? Here is the class:
const got = require("got")
class Checker {
constructor() {
this.url
this.logData = this.logData.bind(this);
this.getData = this.getData.bind(this);
}
async getData(url) {
const response = await got(url);
const data = await response.body;
return data;
}
async logData(first, second, threshold) {
let data = await this.getData(this.url)
console.log("received " + data.body);
}
}
I am trying to mock "getData" so I can write a unit test for "logData". Do I need to mock out the entire "got" module? Thanks.
If you change invoking got to got.get you should be able to have a working test like so:
const got = require('got');
const Checker = require('../index.js');
describe("some test", () => {
beforeEach(() => {
jest.spyOn(got, 'get').mockResolvedValue({ response: { body: { somekey: "somevalue" } } } );
});
it("works", async () => {
new Checker().getData();
expect(got.get).toBeCalledTimes(1);
})
})
One approach is to use dependency injection. Instead of calling 'got' directly, you can 'ask for it' in the class constructor and assign it to a private variable. Then, in the unit test, pass a mock version instead which will return what you want it to.
const got = require("got");
class Checker {
constructor(gotService) {
this.got = gotService;
this.logData = this.logData.bind(this);
this.getData = this.getData.bind(this);
}
async getData(url) {
const response = await this.got(url);
const data = await response.body;
return data;
}
async logData(first, second, threshold) {
let data = await this.getData(this.url)
console.log("received " + data.body);
}
}
//real code
const real = new Checker(got);
//unit testable code
const fakeGot = () => Promise.resolve(mockedData);
const fake = new Checker(fakeGot);
Here is what we are doing:
'Inject' got into the class.
In the class, call our injected version instead of directly calling the original version.
When it's time to unit test, pass a fake version which does what you want it to.
You can include this directly inside your test files. Then trigger the test that makes the Http request and this will be provided as the payload.
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { eth: 0.6, btc: 0.02, ada: 1 } }),
})
);
it('should return correct mock token values', async () => {
const addresses = ["mockA", "mockB", "mockC"];
const res = await getTokenData(addresses);
expect(res.data).toEqual({ eth: 0.6, btc: 0.02, ada: 1 });
});

How can i test a TypeORM repository method with local dependency

I'm new to Node and I'm trying to test a TypeORM custom Repository with Mocha and Sinon, without hitting the database.
My Repository has a method that takes 2 parameters and returns a Promise. It uses a local query builder and I would like to spy it (the queryBuilder) to know how many times its methods are called. This is my custom Repository:
#EntityRepository(Pratica)
export class PraticaRepository extends Repository<Pratica> {
list(targa?: string, tipoVeicolo?: string): Promise<Pratica[]> {
fileLogger.log('info','inizio - targa: %s; tipoVeicolo %s.', targa, tipoVeicolo);
let queryBuilder: SelectQueryBuilder<Pratica> = this.createQueryBuilder("p")
.leftJoinAndSelect("p.stato", "stato")
.leftJoinAndSelect("p.microstato", "microstato");
let filtered: boolean = false;
if(targa && targa !== ""){
fileLogger.debug("Applico filtro targa");
filtered = true;
queryBuilder.where("p.targa = :targa", {targa: targa});
}
if(tipoVeicolo && tipoVeicolo !== ""){
if(!filtered){
fileLogger.debug("Applico filtro tipoVeicolo");
filtered = true;
queryBuilder.where("p.tipoVeicolo = :tipoVeicolo", {tipoVeicolo: tipoVeicolo});
}else{
fileLogger.debug("Applico filtro tipoVeicolo come parametro aggiuntivo");
queryBuilder.andWhere("p.tipoVeicolo = :tipoVeicolo", {tipoVeicolo: tipoVeicolo});
}
}
fileLogger.log('debug', "Sql generato: %s", queryBuilder.getSql);
fileLogger.info("fine");
return queryBuilder.getMany();
}
I've tryed something like the following:
describe('PraticaRepository#list', () => {
it.only('should call getMany once', async () => {
let result = new Promise((resolve,reject) => {
resolve(new Array(new Pratica(), new Pratica()))
});
let getMany = sinon.stub().returns(result);
typeorm.createQueryBuilder = sinon.stub().returns({
select: sinon.stub(),
from: sinon.stub(),
leftJoinAndSelect: sinon.stub(),
where: sinon.stub(),
orderBy: sinon.stub(),
getMany: getMany
})
let cut = new PraticaRepository();
const appo = cut.list('','');
sinon.assert.calledOnce(getMany);
});
})
But obviously i get the following error:
1) PraticaRepository#list
should call getMany once:
TypeError: Cannot read property 'createQueryBuilder' of undefined
at PraticaRepository.Repository.createQueryBuilder (src\repository\Repository.ts:50:29)
at PraticaRepository.list (src\repositories\PraticaRepository.ts:12:62)
because the query builder I'm stubbing is not the one instantiated inside the Repository method. My questions:
Is it possible to spy a method like this?
Is this method "Unit Testable"? Or should I test it only against some functional/integration test.
Thank you in advance.
Thanks to the suggestions of #oligofren this is my final solution:
let sandbox;
let createQueryBuilderStub;
let mock;
let fakeQueryBuilder = new SelectQueryBuilder<Pratica>(null);
beforeEach(() => {
sandbox = sinon.createSandbox();
mock = sandbox.mock(fakeQueryBuilder);
createQueryBuilderStub = sandbox.stub(Repository.prototype,
'createQueryBuilder').withArgs("p").returns(fakeQueryBuilder);
});
afterEach(() => {
sandbox.restore();
});
describe('PraticaRepository#list', () => {
it('should get the result with no filters', async () => {
mock.expects('leftJoinAndSelect').twice().returns(fakeQueryBuilder);
mock.expects('where').never();
mock.expects('andWhere').never();
mock.expects('getSql').once();
mock.expects('getMany').once();
let cut = new PraticaRepository();
const appo = cut.list();
sinon.assert.calledOnce(createQueryBuilderStub);
mock.verify();
});
})

Best way to pass data to a Mocha test that's run programmatically?

Trying to solve a problem where I can't seem to pass dynamically gathered data to Mocha tests.
Here is the logic of my application:
Client submits their Github url. Request is made to Express/Node application.
Express/Node application takes repo and username and makes request to Github API for data and adds the content of the files to an object as base64.
The object with the files are passed to the relevant test files and then executed.
The results are processed and preliminary grades are created. These are then sent back to the client.
Here is what a test file can look like:
const chai = require('chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
const expect = chai.expect;
const base64 = require('base-64');
const HTML_CONTENT = require('../../00-sandbox-files/basic-portfolio-solution.json').html;
const CSS_CONTENT = require('../../00-sandbox-files/basic-portfolio-solution.json').css;
const decodedCSS = base64.decode(CSS_CONTENT[1].content);
const cheerio = require('cheerio');
const juice = require('juice');
let decodedHTMLcontact;
let decodedHTMLindex;
let decodedHTMLportfolio;
for (const obj in HTML_CONTENT) {
if (HTML_CONTENT[obj].path == "contact.html") {
decodedHTMLcontact = base64.decode(HTML_CONTENT[obj].content);
} else if (HTML_CONTENT[obj].path == "index.html") {
decodedHTMLindex = base64.decode(HTML_CONTENT[obj].content);
} else if (HTML_CONTENT[obj].path == "portfolio.html") {
decodedHTMLportfolio = base64.decode(HTML_CONTENT[obj].content);
}
}
tests = function (html, css) {
describe('HTML Elements tests that should pass for contact.html', function () {
let $ = cheerio.load(decodedHTMLcontact);
describe('HTML Elements that should exist in contact.html', function () {
it('should contain a header element', function () {
expect($('body').find('header').length).to.equal(1);
});
it('should contain a section element', function () {
expect($('body').find('section').length).to.equal(1);
});
it('should contain several anchor elements', function () {
expect($('nav').find('a').length).to.be.at.least(3, 'You need an additional anchor elements for your navigation elements');
});
it('should contain an h1 element', function () {
expect($('body').find('h1').length).to.equal(1);
});
it('should contain a form element', function () {
expect($('body').find('form').length).to.equal(1);
});
it('should contain a footer element', function () {
expect($('body').find('footer').length).to.equal(1);
});
});
Here is the execution file for the Mocha tests:
const Mocha = require('mocha');
// Instantiate a Mocha instance.
const mocha = new Mocha();
const HW_PORTFOLIO_PATH = './server/05-run-testing-suite/HW-Week1-portfolio-wireframe/HW-1-portfolio.js';
function homeworkRouter(contentObj, repo) {
switch (repo) {
case "Basic-Portfolio":
mocha.addFile(HW_PORTFOLIO_PATH);
break;
case "HW-Wireframe":
mocha.addFile('./server/05-run-testing-suite/HW-Week1-portfolio-wireframe/HW-1-wireframe.js');
break;
default:
console.log("No homework provided");
break;
}
}
module.exports = {
// Run the tests and have info about what can be returned
runTests: function(contentObj, repo) {
homeworkRouter(contentObj, repo);
console.log("Content Object", contentObj);
console.log("Repo", repo);
mocha.run()
.on('fail', function (test, err) {
console.log('Test fail');
console.log(err);
})
.on('end', function () {
console.log('All done');
});
}
}
Solutions we've come up with involve using vm(), setting data into globals, and/or building up and tearing down files. I'd like a solution that it's much more efficient and doesn't pollute global.

sinon stub fails for promise functions if not exported within class

I'm trying to get sinon.stub to work for async function. I have created promiseFunction.js:
let functionToBeStubbed = async function() {
return ("Text to be replaced by stub.");
};
let promiseFunction = async function() {
return(await functionToBeStubbed());
};
module.exports = {
promiseFunction: promiseFunction,
functionToBeStubbed: functionToBeStubbed
};
and test promiseFunction.spec.js:
let functionstobestested = require('./promiseFunction.js');
describe('Sinon Stub Test', function () {
var sandbox;
it('should return --Text to be replaced by stub.--', async function () {
let responsevalue = "The replaced text.";
sandbox = sinon.sandbox.create();
sandbox.stub(functionstobestested, 'functionToBeStubbed').resolves(responsevalue);
//sandbox.stub(functionstobestested, 'functionToBeStubbed').returns(responsevalue);
let result = "Empty";
console.log(`BEFORE: originaldata = ${result}, value = ${responsevalue}`);
result = await functionstobestested.promiseFunction();
console.log(`AFTER: originaldata = ${result}, value = ${responsevalue}`);
expect(result).to.equal(responsevalue);
sandbox.restore();
console.log("AFTER2: Return value after restoring stub: " + await functionstobestested.promiseFunction());
});
});
when running the test, I will get
test failure
If I modify export slightly, it still fails:
var functionsForTesting = {
promiseFunction: promiseFunction,
functionToBeStubbed: functionToBeStubbed
};
module.exports = functionsForTesting;
I do not understand why this test fails, as it should pass. If I change the way I export functions from promiseFunction.js - module, the test pass correctly. Revised promiseFunction.js:
const functionsForTesting = {
functionToBeStubbed: async function() {
return ("Text to be replaced by stub.");
},
promiseFunction: async function() {
return(await functionsForTesting.functionToBeStubbed());
};
module.exports = functionsForTesting;
Test pass
What's wrong in my original and modified way to export functions?

Resources