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)
Related
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
I want to delete a file and wait for the deletion to succeed before moving forward. I have used unlink function inside a promise to get the result, but when unlink done successfully then I am getting the result from the promise if there is any kink of error while deleting the file the promise does not return any error.
Service:
public removeUserImage(
user: User,
): Promise<NodeJS.ErrnoException | boolean> {
const pathToRemoveImage = 'src/public/uploads'+ '/' + user.image_url;
return new Promise((resolve, reject) => {
unlink(pathToRemoveImage, (error) => {
if (error) reject(error);
resolve(true);
});
});
}
Controller:
const isFileRemoved = await this._userService.removeUserImage(user);
//This block not excuting
if (!isFileRemoved) {
throw new InternalServerErrorException(
'Error occurred while trying to remove file.',
);
}
Your promise rejects if there's an error. When using await, you need to wrap the code in try..catch in order to handle any failures
try {
await this._userService.removeUserImage(user);
} catch (err) {
console.error(err);
throw new InternalServerErrorException(
'Error occurred while trying to remove file.'
);
}
FYI, you can (and should) use the Promises API versions of the fs functions
import { unlink } from "node:fs/promises";
public removeUserImage({ image_url }: User): Promise<void> {
const pathToRemoveImage = `src/public/uploads/${image_url}`;
return unlink(pathToRemoveImage);
}
If you wanted your method to always resolve with a Boolean, you'd want something like
return unlink(pathToRemoveImage)
.then(() => true) // resolve with "true" for success
.catch((err) => {
console.error("removeUserImage", image_url, err);
return false; // resolve with "false" for failure
});
The error will always go to catch block,
try {
await this._userService.removeUserImage(user);
} catch (err) {
console.error(err);
throw new InternalServerErrorException(
'Error occurred while trying to remove file.'
);
}
Suggestion: You don't need to convert unlink(callback) to promise fs has promise function also, check this
const fs = require('fs');
const fsPromises = fs.promises;
public removeUserImage(
user: User,
): Promise<void> {
const pathToRemoveImage = 'src/public/uploads'+ '/' + user.image_url;
return fsPromises.unlink(pathToRemoveImage);
}
The test below is passing. However there'd be no coverage. Apparently the promise does not resolve which is always in pending state. What I did is to break the Promise chain and put the pieces in two different functions to make it look like BDD style.
How do I get that covering my code. Any help would be much appreciated?
NodeJs 14.17.3
ts-jest 27.4.1
Jest 27.4.1.
const request = require('supertest')
let res:Promise<any>
let a:string
let b:string
it('should return ....', async () => {
given('', '')
when()
then()
}
async function given(a:string, b:string) {
// assignments go here
}
async function when() {
cons runPro = async (): Promise<any> => {
return new Promise((resolve, reject) => {
request(baseurl).get('theurl').then((value:any) => {
resolve(value.body)
}).catch(error => {
reject(err)
})
}).catch(error => {
//
}
}
res = runPro()
}
async function then() {
res.then(value => {
expect(value).toEqual("")
}
}
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
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);