Jest testing Promise.all assignment to variable when rejection - javascript

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

Related

Promise test case failing only for Node 16

We are currently upgrading from Node v12 to Node v16.
After the node version update, the following test is failing with this error.
[UnhandledPromiseRejection: 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(). The promise rejected with
the reason "Error".] { code: 'ERR_UNHANDLED_REJECTION'
But why does a node version cause this.
This error stops if I remove the .catch() from the test case.
But it is still incorrect outcome cos the snapshot no longer matches if I do that.
This is the test.
it('should test', async () => {
const thePromise = Promise.resolve({
json: () => {
Promise.reject(new Error());
},
});
// validateData is fetch call.
validateData.mockReturnValueOnce(thePromise);
const rendered = renderModule({ state: { code: 'mockRefCode' } });
rendered.find('#btn').simulate('click');
await ((thePromise)).then().catch(); // If i remove this catch, above error gone but wrong snapshot (null snapshot).
expect(rendered.find('#error')).toMatchSnapshot();
});
This is the code being tested.
export const validateCode = async (env, code) => fetch(`www.getapiresponse.example`);
export const SomeReactComponent(props) {
…
const [validatingCode, setValidatingCode] = useState(false);
const [validationError, setValidationError] = useState('');
const validateData = () => {
if (!code) {
setValidationError(langPack.emptyField);
return;
}
setValidatingCode(true);
validateCode(environment, code.trim())
.then((response) => response.json())
.then((body) => {
if (body.response_status_code !== '200') {
// some logging
} else {
setValidationError('');
}
})
.catch(() => setValidationError(langPack.serviceError))
.finally(() => setValidatingCode(false));
};
…
}
If I remove the .catch(), the snapshot ends up as null.
The correct snapshot should be something like following:
- <div
- className="error"
- id="error"
- >
- service
- </div>

Jest mock unnable to pick up error throw?

I have some code that calls an api. I am testing the functionality that is nested in try catches, and checking the correct behaviour happens when I throw an error with certain values.
async function createDeployment(namespace, config, name) {
try {
await k8sDeploymentApi.createNamespacedDeployment(namespace, config, true);
console.log('Successful deployment');
return Promise.resolve(true);
} catch (e) {
console.log(JSON.stringify(e));
if (e.response.statusCode === HTTP_CONFLICT) {
try {
await k8sDeploymentApi.replaceNamespacedDeployment(name, namespace, config, true);
console.log('Successfully replaced deployment');
return Promise.resolve(true);
} catch (error) {
return Promise.reject(error);
}
}
return Promise.reject(e);
}
}
I am mocking out the api calls node module here
var mockListNamespacedIngress;
var mockCreateNamespacedDeployment;
var mockReplaceNamespacedDeployment;
jest.mock('#kubernetes/client-node', () => {
mockListNamespacedIngress = jest.fn();
mockCreateNamespacedDeployment = jest.fn();
mockReplaceNamespacedDeployment = jest.fn();
return {
KubeConfig: jest.fn().mockImplementation(() => ({
loadFromCluster: jest.fn(),
loadFromDefault: jest.fn(),
makeApiClient: () => ({
listNamespacedIngress: mockListNamespacedIngress,
createNamespacedDeployment: mockCreateNamespacedDeployment,
replaceNamespacedDeployment: mockReplaceNamespacedDeployment,
}),
})),
};
});
And running the test here
it.only('Should return a log of success if replaced deployment resolves', async () => {
mockCreateNamespacedDeployment.mockRejectedValueOnce(() =>
Promise.reject(new Error({ response: { statusCode: 409 } })),
);
mockReplaceNamespacedDeployment.mockResolvedValueOnce(true);
// When
await expect(createDeployment()).resolves.toEqual(true);
});
However on the console.log(JSON.stringify(e)), I am only ever getting undefined coming back. So it does seem to throw the error, but gives no error object from the catch. Here is my full stacktrace
● Kubernetes deployment script tests › Kubernetes Calls › Should return a log of success if replaced deployment resolves
expect(received).resolves.toEqual()
Received promise rejected instead of resolved
Rejected to value: [TypeError: Cannot read property 'statusCode' of undefined]
143 |
144 | // When
> 145 | await expect(createDeployment()).resolves.toEqual(true);
| ^
146 | });
147 | });
148 |
at expect (node_modules/expect/build/index.js:134:15)
at Object.it.only (src/__tests__/index.test.js:145:13)
console.log
Successful deployment
at createDeployment (src/index.js:73:13)
console.log
undefined
at createDeployment (src/index.js:76:13)
You can mock an async function throwing an Error like this
mockCreateNamespacedDeployment.mockRejectedValueOnce(new Error({ response: { statusCode: 409 } });
and then you can expect
await expect(createDeployment()).rejects.toThrow();
Btw in your async function you can replace all of the
- Promise.resolve(xxx)
+ return xxx
// and
- Promise.reject(xxx)
+ throw xxx

Jest , testing Asynchronous JS code always failing

I am trying to test my node app.js script in which I have an asynchronous request sendMessageRequest () to a function sendSmtpMessage() [ a Promise ]
app.js
const sendSmtpMessage = require("./sendSmtpMessage.js");
const keys = {....};
const mailOptions = {...}
const sendMessageRequest = async () => {
try {
const result = await sendSmtpMessage(keys,mailOptions);
console.log("... SEND MSG REQUEST FULLFILLED: ", result);
} catch(err){
console.log("... SEND MSG REQUEST FAILED: ");
}
};
sendMessageRequest();
I wrote the following app.spec.js, according to doc on Testing asynchronous code ( with async/await); but I guess my sendSmtpMessage() mocking is wrong...
app.spec.js
jest.mock("../sendSmtpMessage.js");
const sendSmtpMessage = require("../sendSmtpMessage.js");
const app = require("../app.js");
// sendSmtpMessage is a mock function
sendSmtpMessage.mockImplementation(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
(oauth2ClientMock.refreshToken !== "undefined")? resolve() : reject()
, 2000
});
})
});
describe('app', () => {
let keys, mailOptions;
beforeEach(() => {
keys = {....};
mailOptions = {....}
});
afterEach(() => {
keys = {};
mailOptions = {};
});
it("should call successfully sendMessageRequest", async () => {
// GIVEN
// WHEN
// THEN
expect.assertions(1);
await expect(sendSmtpMessage).resolves.toBe("OK");
});
it("should call unsuccessfully sendMessageRequest", async () => {
// GIVEN
// WHEN
keys.oauth.refresh_token = null;
// THEN
expect.assertions(1);
await expect(sendSmtpMessage).rejects.toBeTruthy();
});
});
As the console.log output is showing errors on both expectations in each test ( on resolve and reject )
console.log
jest --detectOpenHandles --coverage "app.spec.js"
FAIL test/app.spec.js
app
✕ should call successfully sendMessageRequest (15ms)
✕ should call unsuccessfully sendMessageRequest (2ms)
● app › should call successfully sendMessageRequest
expect(received).resolves.toBe()
received value must be a Promise.
Received:
function: [Function mockConstructor]
52 | // THEN
53 | expect.assertions(1);
> 54 | await expect(sendSmtpMessage).resolves.toBe("OK");
| ^
55 | });
56 |
57 | it("should call unsuccessfully sendMessageRequest", async () => {
at Object.toBe (node_modules/expect/build/index.js:158:13)
at Object.toBe (test/app.spec.js:54:44)
● app › should call successfully sendMessageRequest
expect.assertions(1)
Expected one assertion to be called but received zero assertion calls.
51 | // WHEN
52 | // THEN
> 53 | expect.assertions(1);
| ^
54 | await expect(sendSmtpMessage).resolves.toBe("OK");
55 | });
56 |
at Object.assertions (test/app.spec.js:53:12)
● app › should call unsuccessfully sendMessageRequest
expect(received).rejects.toBeTruthy()
received value must be a Promise.
Received:
function: [Function mockConstructor]
61 | // THEN
62 | expect.assertions(1);
> 63 | await expect(sendSmtpMessage).rejects.toBeTruthy();
| ^
64 | });
65 |
66 | });
at Object.toBeTruthy (node_modules/expect/build/index.js:203:13)
at Object.toBeTruthy (test/app.spec.js:63:43)
● app › should call unsuccessfully sendMessageRequest
expect.assertions(1)
Expected one assertion to be called but received zero assertion calls.
60 | keys.oauth.refresh_token = null;
61 | // THEN
> 62 | expect.assertions(1);
| ^
63 | await expect(sendSmtpMessage).rejects.toBeTruthy();
64 | });
65 |
at Object.assertions (test/app.spec.js:62:12)
Where am I wrong ? I don't understand very well the testing process of such plain js scripts... ( use to work with vue.js, test-utils ...)
thanks for feedback and eventually on any link to make me understanding the test unit in such case...
you are not awaiting the sendMessageRequest method call itself
const sendSmtpMessage = require("./sendSmtpMessage.js");
const keys = {....};
const mailOptions = {...}
const sendMessageRequest = async () => {
try {
const result = await sendSmtpMessage(keys,mailOptions);
console.log("... SEND MSG REQUEST FULLFILLED: ", result);
} catch(err){
console.log("... SEND MSG REQUEST FAILED: ");
}
};
(async function() {
await sendMessageRequest();
})();

Assertion error when testing async function with mocha and chaiAsPromised

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);

Testing Promises with Mocha

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
});

Categories

Resources