Mock imported function with jest in an await context - javascript

We are creating a node app, based on express, to read from a static local file and return the JSON within the file.
Here is our json-file.js with our route method:
const readFilePromise = require('fs-readfile-promise');
module.exports = {
readJsonFile: async (req, res) => {
try {
const filePath = 'somefile.json';
const file = await readFilePromise(filePath, 'utf8');
res.send(file);
} catch(e) {
res.status(500).json(e);
}
},
};
we use a third party module, fs-readfile-promise which basically turns node readFileSync into a promise.
But we struggle to mock implementation of this third party, to be able to produce two tests: one based on simulated read file (promise resolved) and one based on rejection.
Here is our test file:
const { readJsonFile } = require('../routes/json-file');
const readFilePromise = require('fs-readfile-promise');
jest.mock('fs-readfile-promise');
const resJson = jest.fn();
const resStatus = jest.fn();
const resSend = jest.fn();
const res = {
send: resSend,
status: resStatus,
json: resJson,
};
resJson.mockImplementation(() => res);
resStatus.mockImplementation(() => res);
resSend.mockImplementation(() => res);
describe('json routes', () => {
beforeEach(() => {
resStatus.mockClear();
resJson.mockClear();
resSend.mockClear();
});
describe('when there is an error reading file', () => {
beforeEach(() => {
readFilePromise.mockImplementation(() => Promise.reject('some error'));
});
it('should return given error', () => {
readJsonFile(null, res);
expect(readFilePromise).lastCalledWith('somefile.json', 'utf8'); // PASS
expect(resStatus).lastCalledWith(500); // FAIL : never called
expect(resSend).lastCalledWith({ "any": "value" }); // FAIL : never called
});
});
});
We tried to place readFilePromise.mockImplementation(() => Promise.reject('some error')); at the top, just after the jest.mock() without more success.
The third party code is basically something like:
module.exports = async function fsReadFilePromise(...args) {
return new Promise(....);
}
How can we mock and replace implementation of the module to return either a Promise.resolve() or Promise.reject() depending on our test setup to make our test case pass within res.send() or res.status() method?

The last 2 assertions never pass because the test doesn't wait for the promise in: const file = await readFilePromise(filePath, 'utf8'); to resolve or reject in this case, therefore res.send or res.status never get called.
To fix it, readJsonFile is async, you should await it in the test:
it('should return given error', async () => {
await readJsonFile(null, res);
...
})
How can we mock and replace implementation of the module to return
either a Promise.resolve() or Promise.reject() depending on our test
setup to make our test case pass within res.send() or res.status()
method
Exactly how you're doing it:
readFilePromise.mockImplementation(() => Promise.reject('some error'));
or
readFilePromise.mockImplementation(() => Promise.resolve('SomeFileContent!'));

Related

nodejs api route testing(mocha) failed due to timeout

i am new to nodejs testing with mocha and chai.right now I am having issue while testing a API route handler with mocha. my route handler code is
exports.imageUpload = (req, res, next) => {
Upload(req,res, async () => {
//get the image files and its original urls (form-data)
let files = req.files['files[]'];
const originalUrls = req.body.orgUrl;
// check the input parameters
if(files == undefined || originalUrls == undefined){
res.status(400).send({status:'failed', message:"input field cannot be undefined"})
}
if(files.length > 0 && originalUrls.length > 0){
//array of promises
let promises = files.map(async(file,index)=>{
//create a image document for each file
let imageDoc = new ImageModel({
croppedImageUrl : file.path,
originalImageUrl: (typeof originalUrls === 'string') ? originalUrls : originalUrls[index],
status: 'New'
});
// return promises to the promises array
return await imageDoc.save();
});
// resolve the promises
try{
const response = await Promise.all(promises);
res.status(200).send({status: 'success', res: response});
}catch(error){
res.status(500).send({status:"failed", error: error})
}
}else{
res.status(400).send({status:'failed', message:"input error"})
}
})
}
the Upload function is just a multer utility to store the imagefile
and my test code is
describe('POST /content/image_upload', () => {
it('should return status 200', async() => {
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
and the output shown after running this code is
1) POST /content/image_upload
should return status 200:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/techv/content_capture/server/ContentServiceLib/api/test/image_upload.test.js)
I believe this updated code should work
describe('POST /content/image_upload', async() => {
await it('should return status 200', async() => {
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
async needs to be added at the top most level as well for it to actually be asynchronous
I think your code is breaking as the upload is taking more then 2 seconds which is the default timeout of mocha as specified in the mocha documentation at https://mochajs.org/#-timeout-ms-t-ms
There are two ways to overcome this problem:
Add a global level timeout while running your command to run the tests --timeout 5s
Add a test specific timeout in the test case itself e.g.
describe('POST /content/image_upload', () => {
it('should return status 200', async() => {
this.timeout(5000) // Timeout
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
Just check the line #3 in above code snippet. more details are here : https://mochajs.org/#test-level

how to assert catch promise with sinon and chai

We have a method in our CLI which uses method returning a promise to print message to user.
exports.handler = (argv) => {
let customUtils = new Utils(argv);
Utils.deploy()
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
}
We are looking for a way to test console errors and process exit in case of deploy() promise rejection.
We tried using sandbox stub then assert in an async test:
describe('when promise is errored', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
processStub = sandbox.stub(process, 'exit');
consoleStub = sandbox.stub(console, 'error');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and log the error before exiting', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.error).to.have.been.called;
});
});
This test doesn't work: AssertionError: expected error to have been called at least once, but it was never called.
The same happens when we expect(process.exit).to.have.been.called;. It's never called.
We successfuly tested the then part in a similary way:
describe('when promise is resolved', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').callsFake(() => Promise.resolve('some text'));
consoleStub = sandbox.stub(console, 'log');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and print success message', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.log).to.have.been.calledWith('Ressource was deployed');
});
});
There are some things to fix the source and test file.
For source file, we must use customUtils to call deploy() function. Since, you can use async/await, convert it from Promise can produce better code.
exports.handler = async argv => { // put async
let customUtils = new Utils(argv);
try {
await customUtils.deploy(); // change to await and use customUtils
console.log(`Ressource was deployed`);
} catch (e) {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
}
};
For test file, nothing changes
describe('when promise is errored', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
processStub = sandbox.stub(process, 'exit');
consoleStub = sandbox.stub(console, 'error');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and log the error before exiting', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.error).to.have.been.called;
expect(process.exit).to.have.been.called; // add it
});
});
UPDATED:
In case want to still use promise, we have to make sure we return the promise.
exports.handler = (argv) => {
let customUtils = new Utils(argv);
return customUtils.deploy() // <== specify return here
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
};
Hope it helps
You need to be able await the result of exports.handler before you test your assertions. You are awaiting it, but exports.handler is not returning the promise, so there's nothing to await in the test — exports.handler returns undefined immediately so the test runs the assertions in the same event loop before console.error can be called.
I'm not sure why you aren't seeing similar problems in the test where the promise resolves. (Maybe worth checking that that test fails properly)
This should help:
exports.handler = (argv) => {
let customUtils = new Utils(argv);
//Utils.deploy() // <- is that a typo?
return customUtils.deploy()
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
}
Also in your tests you are creating a spy with:
consoleStub = sandbox.stub(console, 'error');
But writing the assertion directly on console.error. I don't think this should work:
expect(console.error).to.have.been.called;
// maybe expect(consoleStub)...
With those changes the test passes for me and (more importantly) fails when I don't call console.error in the catch.

How do I unit test the result of a 'then' of a promise in JavaScript?

I'm using TypeScript to write a very simple service that utilizes the AWS SDK. My Jest unit tests are passing, but the coverage reports are saying that the line 'return result.Items' is not covered. Can anyone tell why this is? Is it a bug in jest?
// service file
/**
* Gets an array of documents.
*/
function list(tableName) {
const params = {
TableName: tableName,
};
return docClient
.scan(params)
.promise()
.then((result) => {
return result.Items;
});
}
// test file
const stubAwsRequestWithFakeArrayReturn = () => {
return {
promise: () => {
return { then: () => ({ Items: 'fake-value' }) };
},
};
};
it(`should call docClient.scan() at least once`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
it('should return result.Items out of result', async () => {
const mockAwsCall = jest
.fn()
.mockImplementation(stubAwsRequestWithFakeArrayReturn);
aws.docClient.get = mockAwsCall;
const returnValue = await db.get('fake-table', 'fake-id');
expect(returnValue).toEqual({ Items: 'fake-value' });
});
The line not covered is the success callback passed to then.
Your mock replaces then with a function that doesn't accept any parameters and just returns an object. The callback from your code is passed to the then mock during the test but it doesn't call the callback so Jest correctly reports that the callback is not covered by your tests.
Instead of trying to return a mock object that looks like a Promise, just return an actual resolved Promise from your mock:
const stubAwsRequestWithFakeArrayReturn = () => ({
promise: () => Promise.resolve({ Items: 'fake-value' })
});
...that way then will still be the actual Promise.prototype.then and your callback will be called as expected.
You should also await the returned Promise to ensure that the callback has been called before the test completes:
it(`should call docClient.scan() at least once`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
The Library chai-as-promised is worth looking at.
https://www.chaijs.com/plugins/chai-as-promised/
Instead of manually wiring up your expectations to a promise’s
fulfilled and rejected handlers.
doSomethingAsync().then(
function (result) {
result.should.equal("foo");
done();
},
function (err) {
done(err);
}
);
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("foo");

How can I test a catch block using jest nodejs?

How can i test the catch block on a es6 Class
const fs = require('fs');
class Service {
constructor(accessToken) {
this.accessToken = accessToken;
}
async getData() { // eslint-disable-line class-methods-use-this
try {
const data = fs.readFileSync(`${__dirname}/models/mockData.json`, { encoding: 'utf8' });
const returnData = JSON.parse(data);
return returnData;
} catch (err) {
return err;
}
}
}
module.exports = Service;
using jest how can i write the test case to cover the catch block also.
You can mock the method readFileSync from fs to force it to return undefined. JSON.parse(undefined) will throw an error, thus you can check the catch side of the code.
fs.readFileSync = jest.fn()
fs.readFileSync.mockReturnValue(undefined);
First of all, in the catch side you should throw the error. Just returning it is not a good practise when managing errors, from my point of view. But there is people doing it.
const fs = require('fs');
class Service {
constructor(accessToken) {
this.accessToken = accessToken;
}
async getData() { // eslint-disable-line class-methods-use-this
try {
const data = fs.readFileSync(`${__dirname}/models/mockData.json`, { encoding: 'utf8' });
const returnData = JSON.parse(data);
return returnData;
} catch (err) {
throw err;
}
}
}
Having this code, you can actually test your catch block code in two different ways with Jest:
beforeEach(() => {
fs.readFileSync = jest.fn();
});
afterEach(() => {
fs.readFileSync.mockClear();
});
test('Async expect test', () => {
fs.readFileSync.mockReturnValue(undefined);
const result = service.getData();
expect(result).rejects.toThrow();
});
test('Async / await test', async() => {
fs.readFileSync.mockReturnValue(undefined);
try {
await service.getData();
} catch (err) {
expect(err.name).toEqual('TypeError');
expect(err.message).toEqual(`Cannot read property 'charCodeAt' of undefined`);
}
});
Both of them imply to mock the readFileSync method from fs module as I suggested before. You can even mock the whole fs module with Jest. Or you could just mock the JSON.parse. There are plenty of options to be able to test the catch block.
Jest has its own method for testing exception, you can use toThrow. It looks something like this
test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow(); // Test the exception here
});
Note
Since your function is asynchronous, try to explicitly define your error, then use await to resolve/reject it, After that you can check for the actual rejection
test('throws on octopus', () => {
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

Locked it method in chai

I have a js file which supplies some db operations. This file works with promises only which can be chained. To test that class I work with an async function.
The problem is, that whenever I work with promises inside my test function the it function gets blocked for every other test later.
Here are two examples:
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
test()
async function test () {
await testGetUser()
console.log('1')
await testGetFaculties()
}
function testGetUser () {
return new Promise((resolve1) => {
describe('test get user', function () {
const db = require('../dbInterface')
it('test get user should be complete', function () {
db.dbFunctions.dropAll()
.then(onResolve => {
return db.dbFunctions.createTable(createTableStatements.createTableStatements.user)
}
)
.then(() => {
console.log('success create user table')
return db.dbFunctions.addUser('1', 'firstName', 'lastName', 'email')
})
.then(resolve => {
return db.dbFunctions.getUser('email', undefined)
})
.then(result => {
expect(result.toString().includes('dummy')).to.equal(false)
})
.then(resolve => {
return db.dbFunctions.dropAll()
})
.then(resolve => {
console.log('resolve')
resolve1()
})
.catch(err => console.log(err))
})
})
})
}
function testGetFaculties () {
return new Promise(resolve => {
describe('test get faculties', function () {
let db
before(function () {
db = require('../dbInterface')
})
console.log('displayed')
it('should work', function () {
console.log('locked')
expect(db.dbFunctions.getFaculties('hsa')).to.be.an('array').that.does.include('Science')
resolve()
})
})
})
}
And this is the output
resolve
1
displayed
As you can see console.log('locked') is not being processed.
What i figured out so far, that I only have this issue when I call expect within a then function. But this is necessary for my tests.
The test () function should contain much more tests, only for this question I shortened it.
And for clarification: If I only test methods type of testGetFaculties () which don't contains another promise chain inside it works like it should.
Any idea why this is like it is?
Most probably the console.log( 'locked' ); doesn't do anything, because your previous test case was not finished at all.
Writing describe, it, before inside a Promise and containing unreturned Promises is something that you should not do.
Much better test case would look like :
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
// You use this in both test cases anyway
const db = require('../dbInterface');
describe('test get user', function () {
it('test get user should be complete', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.dropAll()
.then(onResolve => { ... } )
...
)
} );
} );
describe('test get faculties', function () {
it('should work', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.getFaculties('hsa')
.then( value => {
// ^ You actually need to test the value of the resolve promise
expect( value ).to.be.an('array').that.does.include('Science');
} )
} );
} );

Categories