So i am trying to test that my async function throws an error when I stub s3GetObject = Promise.promisify(s3.getObject.bind(s3)) to be rejected with blah however i am getting that my function is not async and it does not throw an error.
below is my main.js file with the tests.js of this:
const Promise = require('bluebird');
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
});
const s3GetObject = Promise.promisify(s3.getObject.bind(s3));
async function getS3File(){
try {
const contentType = await s3GetObject(s3Params);
console.log('CONTENT:', contentType);
return contentType;
} catch (err) {
console.log(err);
throw new Error(err);
}
};
Testing:
/* eslint-env mocha */
const rewire = require('rewire');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const sinon = require('sinon');
const chaiAsPromised = require('chai-as-promised');
chai.should();
chai.use(sinonChai);
chai.use(chaiAsPromised);
describe('Main', () => {
describe('getFileFromS3', () => {
let sut, getS3File, callS3Stub;
beforeEach(() => {
sut = rewire('../../main');
getS3File = sut.__get__('getS3File');
sinon.spy(console, 'log');
});
afterEach(() => {
console.log.restore();
});
it('should be a function', () => {
getS3File.should.be.a('AsyncFunction');
});
describe('with error', () => {
beforeEach(() => {
callS3Stub = sinon.stub().rejects('blah');
sut.__set__('s3GetObject', callS3Stub);
getS3File = sut.__get__('getS3File');
});
it('should error with blah', async () => {
await getS3File.should.throw();
//await console.log.should.be.calledWith('blah');
});
});
});
});
The errors I am getting are:
1) Main
getFileFromS3
should be a function:
AssertionError: expected [Function: getS3File] to be an asyncfunction
at Context.it (test\unit\main.spec.js:28:27)
2) Main
getFileFromS3
with error
should error with blah: AssertionError: expected [Function: getS3File] to throw an error
UnhandledPromiseRejectionWarning: Error: blah
UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 228)
As explained in this answer, it doesn't matter whether a function is async or not, as long as it returns a promise. Chai relies on type-detect to detect types and detects async function as function.
It should be:
getS3File.should.be.a('function');
async functions are syntactic sugar for promises, they don't throw errors but return rejected promises.
It should be:
getS3File().should.be.rejectedWith(Error);
Related
I'm developing tests for my class:
export class DbAuthentication implements Authentication {
private readonly loadAccountByEmailRepository: LoadAccountByEmailRepository
private readonly hashComparer: HashComparer
private readonly tokenGenerator: TokenGenerator
constructor (
loadAccountByEmailRepository: LoadAccountByEmailRepository,
hashComparer: HashComparer,
tokenGenerator: TokenGenerator
) {
this.loadAccountByEmailRepository = loadAccountByEmailRepository
this.hashComparer = hashComparer
this.tokenGenerator = tokenGenerator
}
async auth (authentication: AuthenticationModel): Promise<string> {
const account = await this.loadAccountByEmailRepository.loadByEmail(authentication.email)
if (account) {
const isValid = await this.hashComparer.compare(authentication.password, account.password)
if (!isValid) {
await this.tokenGenerator.generate(account.id)
}
}
return ''
}
}
I used spyon to make my dependencies throws an error:
That worked:
test('Should throw if HashComparer throws', async () => {
const { sut, hashComparerStub } = makeSut()
jest.spyOn(hashComparerStub, 'compare').mockReturnValueOnce(new Promise((resolve, reject) => reject(new Error())))
const promise = sut.auth(makeFakeAuthentication())
await expect(promise).rejects.toThrow()
})
But this breaks my test and returns an error on the jest:
test('Should throw if TokenGenerator throws', async () => {
const { sut, tokenGeneratorStub } = makeSut()
jest.spyOn(tokenGeneratorStub, 'generate').mockReturnValueOnce(new Promise((resolve, reject) => reject(new Error())))
const promise = sut.auth(makeFakeAuthentication())
await expect(promise).rejects.toThrow()
})
I thought it should work because I'm actually doing the exact same thing in both code blocks. I've also used the same strategy in other parts of my development, but I'm getting this error when running the tests
Error:
at /home/cassius/trybe/projetos/pessoais/surveyApi/src/data/usecases/authentication/db-authentication.spec.ts:119:108
at new Promise (<anonymous>)
at Object.<anonymous> (/home/cassius/trybe/projetos/pessoais/surveyApi/src/data/usecases/authentication/db-authentication.spec.ts:119:68)
at Promise.then.completed (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/utils.js:289:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/utils.js:222:10)
at _callCircusTest (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/run.js:248:40)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at _runTest (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/run.js:184:3)
at _runTestsForDescribeBlock (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/run.js:86:9)
at _runTestsForDescribeBlock (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/run.js:81:9)
at run (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/run.js:26:3)
at runAndTransformResultsToJestFormat (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:120:21)
at jestAdapter (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)
at runTestInternal (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-runner/build/runTest.js:367:16)
at runTest (/home/cassius/trybe/projetos/pessoais/surveyApi/node_modules/jest-runner/build/runTest.js:444:34)
I have a Promise.all with async calls to three different services and assigns the resulting lists to three variables
const { getList1 } = require('./module1');
const { getList2 } = require('./module2');
const { getList3 } = require('./module3');
...
const funcToTest = async (params) => {
...
const [list1, list2, list3] = await Promise.all([
getList1(key),
getList2(key),
getList3(key),
]);
// do some stuff
//return promise
}
Here is the getList1 snippet
// ./module1
exports.getList1 = async (param1) => {
...
try {
// make a request that returns a list
return list;
} catch (err) {
if ( err.statusCode === 404) {
return [];
}
throw err;
}
};
To test the function, the Jest case should reject with an error when module1's getList1 request fails
const app = require('../../app');
const module1 = require('../../module1');
const module2 = require('../../module2');
const module3 = require('../../module3');
const nerdStore = require('../../nerdStore');
...
// setup stuff
jest.mock('../../module1');
beforeEach(() => {
module1.getList1
.mockResolvedValue([1,2,3]);
// setup more mocks, stuff .etc
});
afterEach(() => {
jest.clearAllMocks();
});
...
describe('Test funcToTest', () => {
it('should reject with an error when an module1 getList1 fails', async () => {
const errorMessage = 'An error occurred';
module1.getList1
.mockRejectedValue(new Error(errorMessage));
const result = await app.funcToTest(testParams);
await expect(result).rejects.toThrow(errorMessage);
});
The jest test case fails and keeps returning the following
Test funcToTest › should reject with an error when an module1 getList1 fails
An error occurred
120 |
121 | module1.getList1
> 122 | .mockRejectedValue(new Error(errorMessage));
expect(received).rejects.toThrow()
Matcher error: received value must be a promise or a function returning a promise
Received has value: undefined
123 |
124 | const result = await app.funcToTest(testParams);
> 125 | await expect(result).rejects.toThrow(errorMessage);
| ^
126 | });
Promise.allSettled([list1, list2, list3]).
then((results) => results.forEach((result) => console.log(result.status)));
I ran the above and it kept saying fulfilled for all
How do you resolve the undefined issue, mock and assert a rejected promise so that it makes the jest test pass?
Thanks
The handler module has the following structure:
handler.js
const isToBeUpdated = async (itemInputToSave) => {
// func implementation;
}
const handler = async (event) => {
// isToBeUpdated is used here within the try-catch block;
// on `catch` there's only console.error logging the error;
}
module.exports = { handler, isToBeUpdated };
I want to write a test the purpose of which is to check that if isToBeUpdated throws an error then the console.error in the catch part is called with the expected error.
I tried a few solutions, none of them worked. For example I tried putting this into a test:
const mockIsToBeUpdated = jest.fn().mockRejectedValueOnce(Error('isToBeUpdated Error'));
jest.mock('/path/to/the/module', () => {
return jest.fn().mockImplementationOnce(() => {
return {
isToBeUpdated: mockIsToBeUpdated,
};
});
});
await vec.handler({ Records: [record] });
expect(consoleError).toHaveBeenCalledWith(Error('isToBeUpdated Error'));
The consoleError function is a mocked console.error and it was done this way:
const consoleError = jest.spyOn(console, 'error').mockImplementation();
But how do I mock the isToBeUpdated function and make it return an error. It's an async function.
I have a function which wraps a third party child-process-promise, which itself wraps spawn in promise.
let spawn = require('child-process-promise').spawn;
run(cmd, args = []) {
return new Promise(async (resolve, reject) => {
let command = spawn(cmd, args);
let childProcess = command.childProcess;
let result = '';
childProcess.stdout.on('data', (data) => {
result += data.toString();
});
try {
const res = await command;
resolve(result);
} catch (err) {
if (err.code && err.code === 'ENOENT') {
reject(`Command "${cmd}" not found`);
} else {
reject('Exec err' + err);
}
}
});
}
Testing the resolve was quite straightforward and I manage to get my stdout data passed to result then detected by chai-as-promised using await expect(shellRun).to.eventually.become('hello world');
Our problem is when we try to test the catch part of our method.
const ERROR = 'someError';
beforeEach(() => {
sandbox = sinon.createSandbox();
spawnEvent = new events.EventEmitter();
spawnEvent.stdout = new events.EventEmitter();
spawnStub = sandbox.stub();
spawnStub.returns({ childProcess: spawnEvent });
spawnStub.withArgs(ERRORED, ARGUMENTS).throws(ERROR));
shell = proxyquireStrict('../../lib/utils/spawnWrapper', {
'child-process-promise': {
spawn: spawnStub
}
}
);
});
afterEach(() => {
sandbox.restore();
});
describe('when a generic error occurs', () => {
it('should reject the promise', async () => {
const shellRun = run(ERRORED, ARGUMENTS);
await expect(shellRun).to.eventually.be.rejectedWith('Exec err' + ERROR);
});
});
We manage to get childProcessPromiseSpawn to throw an error conditionally by playing with ou spawnStub.withArgs. But a timeout is encountered:
(node:15425) UnhandledPromiseRejectionWarning: Error: someError
(node:15425) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 838)
1 failing
1) run method
when a generic error occurs
should reject the promise:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
We tried spawnStub.withArgs(ERRORED, ARGUMENTS).rejects instead of throws without more success. Changing someError for new Error('someError') doesn't work either.
We also tried to catch at test level
try {
await run(ERRORED, ARGUMENTS);
} catch (e) {
expect(e).to.equal('Exec err' + ERROR);
}
But the timeout still occurs.
This depend of which testing library you are using. Each library has a dedicated timeout.
For mocha you can define it in the test suite or for a unique test
https://mochajs.org/#timeouts
I've been trying to test the following code using Mocha, but I always get the error.
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test
The code I want to test is as follows.
'use strict'
const Promise = require('bluebird');
const successResponse = {status: 'OK'};
const failResponse = {status: 'FAIL'};
function dbStatusSuccess () {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(successResponse);
}, 2010);
});
}
function dbStatusFail () {
return new Promise(function(resolve, reject) {
setTimeout(() => {
reject(failResponse);
}, 2000);
});
}
module.exports = {
dbStatusSuccess,
dbStatusFail
}
and here are my tests.
'use strict'
const Promise = require('bluebird');
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
const resp = {status:'OK'};
const resp2 ={status: 'FAIL'};
describe('healthy-check end point', () => {
it('should return successful response when connected to database', () => {
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
}).catch( (err) => {
console.log(err);
return expect(err).to.deep.equal(resp2);
});
});
});
I also get an error { AssertionError: expected { status: 'OK' } to equal { status: 'OK' } in the console. Which I believe is from loggin the err from the .catch function.
EDIT 1.
Removed the reject function from dbStatusSuccess function.
The issue is with the promises taking 2 seconds to complete/fail. If the time set in setTimeout is less than 2 seconds, the tests will pass.
The default timeout in your test seems to be 2000ms. Your code obviously takes longer to complete. Therefore, you have to up the timeout limit. As noted here you shouldn't use arrow functions so you can safely access this.
Then you can increase your timeout like so:
'use strict'
const Promise = require('bluebird');
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
const resp = {status:'OK'};
const resp2 ={status: 'FAIL'};
describe('healthy-check end point', () => {
it('should return successful response when connected to database', function() {
this.timeout(3000);
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
}).catch( (err) => {
console.log(err);
return expect(err).to.deep.equal(resp2);
});
});
});
Then your test should run as expected.
'use strict'
const Promise = require('bluebird');
const chai = require('chai');
chai.use(require('chai-string'));
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
describe('healthy-check end point', function() {
it('should return successful response when connected to database', function(done) {
const resp = {status: 'OK'};
healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
expect(res).to.equal(resp);
done();
}).catch(done);
});
});
In code example I don't return promise, so I need use callback. Usage callback in your async tests helps avoid Error: timeout of 2000ms exceeded
Don't use arrow function in descibe and it. More info
You should use the done callback, for example:
it('reads some file', function(done) {
fs.readFile('someFile.json', function(err, data) {
if (err) return done(err);
assert(data != null, "File should exist.");
done();
});
});
What's happening is the test ('it' function) returns before your promise resolves; using done means the test won't finish until you call done() when the promises resolves.
See
http://tobyho.com/2015/12/16/mocha-with-promises/
and
https://mochajs.org/#working-with-promises
Well, I just found the problem, your test is tricky.
You set the timeout timer to 2010ms but Mocha default execution time is 2000ms, so you will always get the error from Mocha.
I still think you shouldn't create .catch block in the returned promise chain, it will stop the promise chain to propagate.
describe('healthy-check end point', () => {
it('should return successful response when connected to database', () => {
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
});
}).timeout(2500); //tell Mocha to wait for 2500ms
});