Jest mocks - how to restore original function between tests - javascript

I have my function:
report.js:
const ctrl = {};
const _ = require('lodash');
const boom = require("boom");
const slackNotifications = require('./../../slack/controllers/notifications');
const reportModel = require("./../models/report");
import validationSchema from "./../_validation/report";
ctrl.addReport = async (req, res) => {
const { body } = req;
try {
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
if (report.reported) {
await slackNotifications.notify('Notify me!');
res.status(200).send({ reported: true });
} else {
throw boom.serverUnavailable("Can't offer report");
}
} catch (err) {
throw boom.badRequest(err);
}
};
module.exports = ctrl;
The validation schema is schema created by using yup.js.
Here are the tests
The problem is throw Error when validation failed test. I've got
TypeError: Cannot read property 'output' of undefined
from 109 line
const { output, output: { payload } } = err;.
But my expected value is error threw by validationSchema.validate and caught in 23rd line of my function.
When I run only this test everything is ok. I've got the correct error with status code and message.
How can I restore the original function validationSchema.validate from this test (84th line)?
I've tried to restore by:
jest.mock('./../_validation/report', () => ({
// validate: jest.fn().mockReset(),
validate: jest.fn().mockClear(),
}));
But I'm not sure that is the correct way.

You can use requireActual in your test to execute the original behaviour for just one test.
This way :
jest.mock('./../_validation/report', () => ({
validate: jest.requireActual('./../_validation/report').validate,
}));
This way, the real validate will be called
Of course, at the next test (or in a beforeEach), you can re-mock your function.

Related

Emitting an error in my jest test doesn't trigger the error handler as expected

My download code relies on listening on listening for events to determine when to fire callbacks, and whether the promise it's in should be resolved or rejected:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', fileName);
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
return new Promise((resolve, reject) => {
const timeout = 20000;
const timer = setTimeout(() => {
console.log('timed out'); // debug log
writer.close();
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
// set up data and writer listeners
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.on('error', (err) => { // added this to see if it would be triggered - it is not
console.log(`did a data error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('error', (err) => {
console.log(`did a writer error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.log(`close called: ${now}`);
console.log(`error is: ${error}`);
console.info(
`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`,
);
clearTimeout(timer);
console.log(`time cleared: ${timer}`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
// finally call data.pipe with our writer
data.pipe(writer);
});
}
I had some issues writing my tests, but I managed to get something that worked, despite feeling slightly messy, based on this advice:
Here is my test, with the relevant bits of my set up:
describe('fetchData', () => {
let dataChunkFn;
let dataErrorFn;
let dataOnFn;
let writerCloseFn;
let writerErrorFn;
let writerOnFn;
let pipeHandler;
beforeEach(() => {
// I've left all the mocking in place,
// to give an idea of what I've set up
const mockWriterEventHandlers = {};
const mockDataEventHandlers = {};
dataChunkFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataErrorFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataOnFn = jest.fn((e, cb) => {
mockDataEventHandlers[e] = cb;
});
writerCloseFn = jest.fn(() => mockWriterEventHandlers.close());
writerErrorFn = jest.fn(() => mockWriterEventHandlers.error());
writerOnFn = jest.fn((e, cb) => {
mockWriterEventHandlers[e] = cb;
});
const getMockData = (pipe) => ({
status: 200,
data: {
pipe,
on: dataOnFn,
},
headers: { 'content-length': 100 },
});
axios.mockImplementationOnce(() => getMockData(pipeHandler));
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
}));
jest.spyOn(console, 'info').mockImplementation(() => {});
jest.spyOn(console, 'log').mockImplementation(() => {});
});
it.only('handles errors from the writer', async (done) => {
console.log('writer error');
expect.assertions(1);
pipeHandler = (writer) => writer.emit('error', new Error('bang'));
try {
await downloadMtgJsonZip();
done.fail('ran without error');
} catch (exception) {
// expect(dataErrorFn).toHaveBeenCalled(); // neither of these are called
expect(writerErrorFn).toHaveBeenCalled();
}
});
I would have expected, that when data(pipe) ran, and the writer emitted a new error, it would have triggered at least one of the error listeners.
The code runs as expected, and it even handles the timeout (which I initially set too low), but this last test doesn't run.
As I commented above, neither of the functions above are called, so the expect.assertions(1); code fails the test.
It's possible I need to fundamentally change how I've written the tests, but I'm not sure how I would do that.
Why doesn't that last test pass?
When the code invokes data.pipe(writer), it's running your pipeHandler function defined in the test. This function takes a given writer object and calls writer.emit(...). I believe the issue is that the writer object being passed in is the one mocked out for fs.createWriteStream(), which doesn't have an emit method defined, so nothing is happening in response to that call. It is likely throwing an error, which you may be able to see in your catch block.
I believe what you want is to invoke the handlers saved by the writerOnFn. One way to do so would be to add a property to the object returned by your mock of fs.createWriteStream named emit and define it as a function that invokes the appropriate handler from inside mockWriterEventHandlers. I haven't tested this code but it would look something like the following
const writerEmitFn = (event, arg) => {
mockWriterEventHandlers[event](arg);
}
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
emit: writerEmitFn,
}));
My guess is that jest is gobbling up the error.
In order to continue running in the case of exceptions, jest could be guarding against ever having to run try and throw.
You could try expecting an error to have been thrown using jest's API.

using sinon to mock a line in a function

I have a function as follows
import config from "../config";
export const encrypt = (recordJSONy, logger) => {
const key = config.encryption.key
if(!key || key == ""){
throw encryptionError.ENCYPTION_KEY_MISSING
}
try{
const decryptedDataStr = TisEncryption.Cipher.encrypt(recordJSON);
return decryptedDataStr;
} catch(e){
console.log(e)
throw encryptionError.ENCRYPTION_FAILED
}
}
I need to write a test for it
describe("help function test", () => {
it("test", () => {
console.log(helperFunctions.encrypt("xxx", loggingService))
});
});
However the above is always returning error because config.encryption.key returns undefined.
I am trying to find a way to kind of mock config.encryption.key to return "xxxxxxx" instead of undefined. The more I look at sinon the more I get confused. Can anyone shed light on how to do so?

Sinon stub not called after promise returned

I'm trying to add more tests to my code with Mocha, Chai and Sinon, however I'm struggling to understand why this second stubbed function isn't recognised as being called.
I have a function that sends an email to a user (I'll test the email functionality later - for now I just want to get a handle on stubbing dependencies that I control)
// EmailSender.js
const models = require('../models');
const User = models.user;
const emailLogger = require('./emailLogger');
class EmailSender {
constructor(subject, emailData) {
this.subject = subject;
this.emailData = emailData;
}
sendToUser() {
let email = this.emailData.email;
User.findOne({ where: { $or: [
{ email: email },
{ workEmail: email },
] } })
.then(function (userData) {
if (userData) {
emailLogger.log('Send to anon - sending to user ' + userData.id);
});
} else {
emailLogger.log('Send to anon - no user found');
}
}
}
And a test file:
const EmailSender = require('../../../helpers/emailSender');
const models = require('../../../models');
const User = models.user;
const emailLogger = require('../../../helpers/emailLogger');
const chai = require("chai");
const sinon = require('sinon');
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe('The emailSender', () => {
let emailData;
beforeEach(() => {
emailData = {
email: 'testemail#eml.co'
};
sinon.stub(User, 'findOne').returns(Promise.resolve());
sinon.stub(emailLogger, 'log');
})
afterEach(() => {
User.findOne.restore();
emailLogger.log.restore();
})
describe('sendToUser method', () => {
it('logs an email if a user is found', () => {
let emailSender = new EmailSender('Email subject', emailData);
emailSender.sendToUser();
expect(User.findOne).to.have.been.calledOnce; // works
expect(emailLogger.log).to.have.been.calledOnce; // doesn't
})
})
});
I can stub the User.findOne() method with Sinon, but when I try and stub the emailLogger.log() method I get into trouble. It appears to call the stub and not the real method, but expect(emailLogger.log).to.have.been.calledOnce returns false.
I've tried adding done() and a fake timer in case there was a delay issue, as well as a range of other things, but no luck so far.
A great trick is to return the promise from the test function, which causes Mocha to wait until the promise completes. Here's how you'd do this:
it('logs an email if a user is found', () => {
const emailSender = new EmailSender('Email subject', emailData);
return emailSender.sendToUser().then( () => {
//check after the sendToUser promise is complete, but before the test is done
expect(User.findOne).to.have.been.calledOnce;
expect(emailLogger.log).to.have.been.calledOnce;
});
});
This has the added benefit that if the promise fails for any reason, the test will fail to (with the correct error).
You don't ever return the promise from the sendToUser function, which means there is no way of knowing when it actually completes. As you are testing an async function in a synchronous manner, that means you are asking if emailLogger.log has been called, before it is being called in the code!
You need to return the promise, and then you can do what Duncan proposes in his answer.

how do i test my async jasmine/nodejs/promise code using Spies

I have a module (example has been simplified) called process-promise which has a single function that takes a Promise as input and processes it - it also calls other functions using modules outside it as follows:
//<process-promise.js>
let User = require('user-module');
let processPromise = (promiseObj) => {
let user = new User();
promiseObj.then((full_name) => {
const [ fname, sname ] = full_name.split(' ');
if (fname && sname) {
user.setDetails(fname, sname);
} else{
console.log('nothing happened');
}
}).catch((err) => {
console.log(err.message);
});
};
module.exports = {
processPromise
};
I am trying to unit test the above function using Jasmine, Rewire and Jasmine spies as per following code
let rewire = require('rewire');
let mod = rewire('process-promise');
describe('process-promise module', () => {
beforeEach(() => {
this.fakeUser = createSpyObj('fake-user', ['setDetails']);
this.fakeUserMod = jasmine.createSpy('fake-user-mod');
this.fakeUserMod.and.returnValue(this.fakeUser)
this.revert = mod.__set__({
User: this.fakeUserMod
});
});
afterEach(() => {
this.revert();
});
it('fakeUser.setDetails should be called', (done) => {
mod.processPromise(Promise.resolve('user name'));
done();
expect(this.fakeUser.setDetails).toHaveBeenCalledWith('user','name');
});
});
I expect that the Spy this.fakeUser.setDetails should get called but i get the message from Jasmine "Expected spy fake-user.setAll to have been called with [ 'user', 'name' ] but it was never called." - the problem seems to be the fact the promise is Async but i've included the done function as other SO questions have suggested but this doesn't seem to resolve the problem for me. What's the issue with my code? most other SO questions relate to angular so don't help with my problem.
You are on the right track, the promise is asynchronous and then done function in your test is called before the promise resolved to a value. The done function is used as a callback to tell the test engine, that all your asynchronous code has completed. It should be called after the promise resolved to a value (or failed for that matter).
In order to do that, you'd need to make the following adjustments to your code:
//<process-promise.js>
let User = require('user-module');
let processPromise = (promiseObj) => {
let user = new User();
// return a promise, to allow a client to chain a .then call
return promiseObj.then((full_name) => {
const [ fname, sname ] = full_name.split(' ');
if (fname && sname) {
user.setDetails(fname, sname);
} else{
console.log('nothing happened');
}
}).catch((err) => {
console.log(err.message);
});
};
module.exports = {
processPromise
};
The test would then look like this:
it('fakeUser.setAll should be called', (done) => {
mod.processPromise(Promise.resolve('user name')).then(() => {
expect(this.fakeUser.setAll).toHaveBeenCalledWith('user','name');
done();
}).catch(done);
});
Be sure to add .catch(done). This will make sure your test fails in case the promise resolves to an error.
Is probable that, by the time your test code execute, the promise has not propagated to the code under test. And simply calling done() doesn't the synchronization magic.
I'm not familiar with rewire so I will share an example using
proxyquire
const proxyquire = require('proxyquire');
describe('process-promise module', () => {
const fakeUser = { setDetails: jasmine.createSpy('setDetails') };
const fakeUserMod = jasmine.createSpy('fake-user-mod').and.returnValue(fakeUser);
const promiseObj = Promise.resolve('user name');
beforeEach((done) => {
const processPromiseMod = proxyquire('process-promise', {
'user-module': fakeUserMod,
});
processPromiseMod.processPromise(promiseObj);
promiseObj.then(() => done());
});
it('fakeUser.setDetails should be called', () => {
expect(fakeUser.setDetails).toHaveBeenCalledWith('user','name');
});
});
Also note that setAll doesn't exist in the fakeUser instance. I guess you mean setDetails instead of setAll.

How do I test custom Koa middleware for error handling?

As part of a migration of an older app from ExpressJs to Koa JS (v1). I've written a piece of middleware to handle any errors that occur. It looks something like this:
module.errors = function * (next) {
try {
yield next;
} catch (err) {
switch(err && err.message) {
case: 'Bad Request':
this.status = 400;
this.body = {message: 'Bad Request'};
brea;
default:
this.status = 500;
this.body = {message: 'An error has occurred'};
}
this.app.emit('error', err, this);
}
}
It gets included in my application like this:
const app = require('koa')();
const router = require('koa-router');
const { errors } = require('./middleware/errors');
app.use(errors)
.use(router.routes());
app.get('/some-request', function *(next){
// request that could error
});
app.listen();
This all works fine, but I'd like to test the middleware with my unit tests, and perhaps because I'm still fairly new to both Koa and Generator functions, I'm struggling to figure out how to do this.
I know that if I import the error handling middleware, I need to pass it a function that will throw an error, but how do I execute the function passed? Does it need to be closure of some description? How do I assert/expect on the values set for the status code and the like?
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let thrower = function(){ throw new Error() }
let errorHandler = errors(thrower());
// mass of confusion
expect(errorHandler.next()).to.throw(Error);
});
});
Koa middlewares are generators (return/yield multiple times) and don't behave like functions, so it feels weird to write unit tests for them. Personally, I suffice with end-to-end test cases.
However, the following might work in your case.
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
describe('errors middleware', () => {
it('returns a 500 on a generic error', () => {
let ctx = { body: {}, status: 404 };
let errorMidIterator = errors().call(ctx, 'NEXT_MID');
// test that it correctly yields to next middleware
expect(errorMidIterator.next().value).should.equal('NEXT_MID');
// simualte an error and test if it correctly sets the body
expect(errorMidIterator.throw(new Error()).done).to.equal(true);
expect(ctx.status).should.equal(500);
});
});
As a side note, I think it is better to export middleware factories from your files rather than plain middleware generator functions. The former gives you more control (i.e. you can possibly inject some of the dependencies, in this case the thrower() function, through the Factory function arguments). My middleware files look like these.
module.exports = function MyMiddleware(options) {
return function *MyMiddleware(next) {
// options.config.foo
// options.httpclient.get(..)
};
}
Lastly koa wraps the generator functions with co, which changes the semantics so unit tests are not that useful (subjective)
you can use this library https://www.npmjs.com/package/co which used by koa.js 1.x to wrap your generator functions and mock the context object.
const co = require('co');
const Emitter = require('events');
const { expect } = require('chai');
const { errors } = require('../middleware/errors');
const wrapped = co.wrap(errors);
const mockApp = new Emitter();
describe('errors middleware', () => {
it('returns a 500 on a generic error', (done) => {
const ERROR_MSG = 'middleware error';
const ctx = {app: mockApp};
const next = function* () {
throw new Error(ERROR_MSG);
}
wrapped.call(ctx, next)
.then(() => {
try {
expect(ctx.status).to.equal(500);
expect(ctx.body.message).to.equal(ERROR_MSG);
done();
} catch (err) {
done(err);
}
})
.catch(err => done(err))
});
});
This is how I solved this problem with Jest, I just created a custom res object and passed it to error handler:
const error = require('../../../middleware/error');
describe('error middleware', () => {
it(' return 500 if there is unhandled error', async () => {
const res = {
status: (c) => {this.c = c; return {send: (s) => {this.s = s; return this}}} ,
c: 200,
s: 'OK',
};
const req = {};
const next = jest.fn();
const err = () => {
throw new Error()
};
const errorHandler = error(err, req, res, next);
expect(errorHandler).toMatchObject({c: 500, s: 'Something failed'});
});
});

Categories