Jest asynchronous tests notify test finished with a failure - unit-testing

I have following test cases, both are passing when I expected the test to be marked as failing:
// testing.test.js
describe('test sandbox', () => {
it('asynchronous test failure', done => {
Promise.resolve()
.then(_ => {
// failure
expect(false).toBeTruthy();// test failed
done();// never get called
})
.catch(err => {// this catches the expectation failure and finishes the test
done(err);
});
});
it('asynchronous test success', done => {
Promise.resolve()
.then(_ => {
// failure
expect(true).toBeTruthy();
done();
})
.catch(err => {
console.log(err);
done(err);
});
});
});
Now the output looks as follows:
PASS src\tests\testing.test.js
test sandbox
√ asynchronous test failure (3ms)
√ asynchronous test success (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.884s
Ran all test suites matching /testing.test/.
console.log src\tests\testing.test.js:10
{ Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Promise.resolve.then._ (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:6:21)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7) matcherResult: { message: [Function], pass: false } }
This is how it started, when I've encountered this for the first time, and it stopped testing any other tests with error,
// sandbox test
describe('test sandbox', () => {
it('asynchronous test failure', done => {
Promise.resolve()
.then(_ => {
// failure
expect(false).toBeTruthy();
done();// expected this to be called, but no
})
});
it('asynchronous test success', done => {
Promise.resolve()
.then(_ => {
// failure
expect(true).toBeTruthy();
done();
})
});
});
will stop the test suite which is not good as well as I want a full test suite done with a results of x failed:
RUNS src/tests/testing.test.js
C:\dev\reactjs\OmniUI\node_modules\react-scripts\scripts\test.js:20
throw err;
^
Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Promise.resolve.then._ (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:6:21)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
and in Jest console log:
{
"resource": "/C:/dev/.../src/tests/testing.test.js",
"owner": "Jest",
"code": "undefined",
"severity": 8,
"message": "Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.",
"source": "Jest",
"startLineNumber": 9,
"startColumn": 1,
"endLineNumber": 9,
"endColumn": 6
}
and here is
// real life example
// actions.js with axios call
export const startTransactionStarted = () => ({
type: 'START_CART_TRANSACTION_STARTED',
});
export const startTransactionFinished = (payload, success) => ({
type: 'START_CART_TRANSACTION_FINISHED',
payload,
success,
});
export const startTransaction = () => (dispatch, getState) => {
const state = getState();
const {
transaction: { BasketId = undefined },
} = state;
if (BasketId !== undefined && BasketId !== null) {
// we already have BasketId carry on without dispatching transaction actions
return Promise.resolve();
}
dispatch(startTransactionStarted());
return Axios.post("https://test:181/api/Basket/StartTransaction", {})
.then(response => {
const {
data: { BusinessErrors, MessageHeader },
} = response;
if (BusinessErrors !== null) {
dispatch(startTransactionFinished(BusinessErrors, false));
return Promise.reject(BusinessErrors);
}
dispatch(startTransactionFinished(MessageHeader, true));
return Promise.resolve(response.data);
})
.catch(error => {
// console.log("Axios.post().catch()", error);
dispatch(startTransactionFinished([error], false));
/* eslint-disable prefer-promise-reject-errors */
return Promise.reject([error]);
});
};
// actions.test.js
import Axios from 'axios';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import {
startTransactionStarted,
startTransaction,
startTransactionFinished
} from 'actions';
const createMockStore = configureMockStore([thunk]);
describe('actions.js', () => {
it('test 404 response', done => {
// create mocked store with least amount of data (only those that are needed by tested code)
const store = createMockStore({
transaction: {
BasketId: null,
},
});
// jest mocked function - we are using it to test that dispatch action worked
const onFulfilled = jest.fn();
const onErrorCalled = jest.fn();
const error = { message: '404 - Error' };
const expectedResponse = [error];
// single test with mock adapter (for axios)
moxios.withMock(() => {
// dispatch action with axios call, pass mocked response handler
store
.dispatch(startTransaction())
.then(onFulfilled)
.catch(onErrorCalled);
// wait for request
moxios.wait(() => {
// pick most recent request which then we will "mock"
const request = moxios.requests.mostRecent();
console.log('moxios.wait()');
request
.respondWith({
status: 404,
error,
})
.then(() => {
console.log('- then()');
expect(onFulfilled).not.toHaveBeenCalled();
expect(onErrorCalled).toHaveBeenCalled();
const actions = store.getActions();
console.log(actions);
expect(actions).toEqual([
startTransactionStarted(),
startTransactionFinished(expectedResponse, false),
]);// this fails
done();// this is never called
})
});
});
});
});
test stops with this error
RUNS src/tests/testing.test.js
C:\dev\reactjs\OmniUI\node_modules\react-scripts\scripts\test.js:20
throw err;
^
Error: expect(received).toEqual(expected)
Expected value to equal:
[{"type": "START_CART_TRANSACTION_STARTED"}, {"payload": [{"message": "404 - Error"}], "success": false, "type": "START_CART_TRANSACTION_FINISHED"}]
Received:
[{"type": "START_CART_TRANSACTION_STARTED"}, {"payload": [[Error: Request failed with status code 404]], "success": false, "type": "START_CART_TRANSACTION_FINISHED"}]
Difference:
- Expected
+ Received
## -2,13 +2,11 ##
Object {
"type": "START_CART_TRANSACTION_STARTED",
},
Object {
"payload": Array [
- Object {
- "message": "404 - Error",
- },
+ [Error: Request failed with status code 404],
],
"success": false,
"type": "START_CART_TRANSACTION_FINISHED",
},
]
at request.respondWith.then (C:\dev\reactjs\OmniUI\src\tests\testing.test.js:101:27)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Is there a way for the asynchronous tests either (first example) to pass done but notify Jest that it failed? or setup jest to dont stop further tests on timeout?

You can call done with a parameter telling Jest the test has failed:
done(error);

Related

Jest Mock nor working as expected throws an error

const axios = "axios";
jest.mock(axios);
axios.get.mockImplementation((url) => {
if(url === process.env.ENHANCED_CLAIM_STATUS_276_DETAILS){
return Promise.resolve({ status: 200, data: claim_response_276 });
}
if(url === process.env.ENHANCED_VALUE_ADDS_277_DETAILS){
return Promise.resolve({ status: 200, data: claim_response_277 });
}
});
i am trying to mock the api responses but it throws this error :
**TypeError: Cannot read properties of undefined (reading 'mockImplementation')**
The moduleName parameter should be a string. See API doc jest.mock(moduleName, factory, options)
An working example:
import axios from "axios";
jest.mock('axios');
describe('74929332', () => {
test('should pass', async () => {
axios.get.mockImplementation((url) => {
return Promise.resolve({ status: 200, data: 'claim_response_276' });
});
const result = await axios.get('http://localhost:3000/api')
expect(result).toEqual({ status: 200, data: 'claim_response_276' })
})
});
Test result:
PASS stackoverflow/74929332/index.test.js (14.419 s)
74929332
✓ should pass (3 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.559 s

Cannot log after tests are done in jestjs

I have written test cases for signin API using jest. After completing all five test of a test suit jest give me following error in log.
Can any body tell Why it is So and how to fix it?
CODE:(signup.test.ts)
import request from 'supertest';
import { TYPES } from '../src/inversify.types'
import { Application } from '../src/app/Application'
import { container } from '../src/inversify.config'
import dotenv from 'dotenv'
import { RESPONSE_CODE } from '../src/utils/enums/ResponseCode'
import { RESPONSE_MESSAGES } from '../src/utils/enums/ResponseMessages'
import { UserSchema } from '../src/components/user/User';
// import jwt from 'jsonwebtoken';
var application: Application
describe("POST / - SIGNUP endpoint", () => {
// var testusers: any;
//This hook is executed before running all test cases, It will make application instance, make it to listen
// on it on port 3000 and add test document in DB
beforeAll(async () => {
// Make enviroment variables available throughout the application
dotenv.config();
// Getting application instance using iversify container
application = container.get<Application>(TYPES.Application);
// Initialize frontside of application
await application.bootstrap();
// Starting Application server on given port
await application.listen(3000);
});
afterAll(
//This hook is executed after running all test cases and delete test document in database
async () =>{
const res = await UserSchema.deleteMany({ Name: { $in: [ "Test User", "Test" ] } });
// `0` if no docs matched the filter, number of docs deleted otherwise
console.log('---------------------->>>>>>>>>>>>>>>>>>>', (res as any).deletedCount);
}
)
it("Signup for user that don\'t exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CREATED);
expect(JSON.parse(response.text)).toEqual(expect.objectContaining({
Message: RESPONSE_MESSAGES.ADDED_SUCESSFULLY,
Data: expect.objectContaining({
Name: 'Test User',
Country: '',
PhoneNumber: '',
// Password: '$2b$10$nIHLW/SA73XLHoIcND27iuODFAArOvpch6FL/eikKT78qbShAl6ry',
Dob: '',
Role: 'MEMBER',
IsEmailVerified: false,
IsBlocked: 'ACTIVE',
IsTokenSent: false,
twoFAStatus: false,
// _id: '5c812e2715e0711b98260fee',
Email: JSON.parse(process.env.TEST_USER).Email
})
})
);
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
it("Signup for user that exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CONFLICT);
expect(JSON.parse(response.text)).toEqual({
Message: RESPONSE_MESSAGES.ALREADY_EXISTS
})
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
});
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.
Cannot log after tests are done. Did you forget to wait for something
async in your test?
Attempted to log "{ accepted: [ 'unverifiedtestuser#abc.com' ],
rejected: [],
envelopeTime: 621,
messageTime: 867,
messageSize: 906,
response: '250 2.0.0 OK 1551945300 f6sm5442066wrt.87 - gsmtp',
envelope:
{ from: 'abc#gmail.com',
to: [ 'unverifiedtestuser#abc.com' ] },
messageId: '<45468449-b5c8-0d86-9404-d55bb5f4g6a3#gmail.com>' }".
at CustomConsole.log (node_modules/jest-util/build/CustomConsole.js:156:10)
at src/email/MailHandler.ts:2599:17
at transporter.send.args (node_modules/nodemailer/lib/mailer/index.js:226:21)
at connection.send (node_modules/nodemailer/lib/smtp-transport/index.js:247:32)
at callback (node_modules/nodemailer/lib/smtp-connection/index.js:435:13)
at stream._createSendStream (node_modules/nodemailer/lib/smtp-connection/index.js:458:24)
at SMTPConnection._actionSMTPStream (node_modules/nodemailer/lib/smtp-connection/index.js:1481:20)
at SMTPConnection._responseActions.push.str (node_modules/nodemailer/lib/smtp-connection/index.js:968:22)
at SMTPConnection._processResponse (node_modules/nodemailer/lib/smtp-connection/index.js:764:20)
at SMTPConnection._onData (node_modules/nodemailer/lib/smtp-connection/index.js:570:14)
I was using the react-native default test case (see below) when Cannot log after tests are done happened.
it('renders correctly', () => {
renderer.create(<App />);
});
Apparently, the problem was that the test ended but logging was still needed. So I tried to make the callback in the test case async, hoping that the test won't terminate immediately:
it('renders correctly', async () => {
renderer.create(<App />);
});
And it worked. However, I have very little clue what the inner working is.
If you are using async/await type in your code, then this error can occur when you are calling async function without await keyword.
In my case, I have defined a function like this below,
async getStatistics(headers) {
....
....
return response;
}
But I have called this method like getStatistics(headers) instead of await getStatistics(headers).
When I included await, it worked fine and the issue resolved.
In my case while using nodejs + jest + supertest the problem was that when I import app from "./app" to my test file to do some stuff with supertest (request(app)), I actually import with app.listen() , because when I'm exporting app, export takes in account app.listen() too, but we don't need app.listen() in tests and it throws an error
"Cannot log after tests are done.Did you forget to wait for something async in your test?"
Here is all in one file(that's the problem!)
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});
export default app;
To solve this issue I created 2 separate folders:
// 1) app.ts
Where I put all stuff for my const app = express(), routes etc and export app
dotenv.config();
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
export default app;
// 2) index.ts
Where I put app.listen() and mongoose.connection() and import app
*import mongoose from "mongoose";
import app from "./app";
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});*
For me I needed to add an await before the expect() call also to stop this error (and an async before the test() callback function).
Also caused and fixed Jest not detecting coverage on the lines in the code throwing the error!
test("expect error to be thrown for incorrect request", async () => {
await expect(
// ^ added this
async () => await getData("i-made-this-up")
).rejects.toThrow(
"[API] Not recognised: i-made-this-up"
);
});
getData() returns an Axios call and in this case an error is caught by catch and re-thrown.
const getData = async (id) => {
return await axios
.get(`https://api.com/some/path?id=${id}`)
.then((response) => response.data)
.catch((error) => {
if (error?.response?.data?.message) {
console.error(error) // Triggered the error
throw new Error("[API] " + error.response.data.message);
}
throw error;
});
};
This happened to me because I had an infinite loop while (true). In my case, I was able to add a method for setting the value of the loop based on user input, rather than defaulting to true.
In my case, the error was caused by asynchronous Redis connection still online. Just added afterall method to quit Redis and could see the log again.
Working on Typescript 4.4.2:
test("My Test", done => {
let redisUtil: RedisUtil = new RedisUtil();
let redisClient: Redis = redisUtil.redis_client();
done();
});
afterAll(() => {
redisClient.quit();
});
I solved it with the env variables:
if (process.env.NODE_ENV !== 'test') {
db.init().then(() => {
app.listen(PORT, () => {
console.log('API lista por el puerto ', PORT)
})
}).catch((err) => {
console.error(err)
process.exit(1)
})
} else {
module.export = app
}
I faced same warnings. However the fix is bit weird:
The jest unit test script import a function (which is not export from src/). After I added the export to the function to be tested. The error disappears.
I had a similar issue:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ".
It was due to a missing static keyword. This code caused the issue:
class MyComponent extends React.Component<Props, State> {
propTypes = {
onDestroy: PropTypes.func,
}
}
It should have been:
class MyComponent extends React.Component<Props, State> {
static propTypes = {
onDestroy: PropTypes.func,
}
}

Saga unit test deepEqual returns false anyway

I am pretty much new with Redux-saga and working on unit test. API call function unit test code returns false even if it looks identical. What am I missing?
[Actions]
export const actions = {
readUsers: {
read: (data) => util.createAction('READ_USER', data),
request: () => util.createAction('READ_REQUEST'),
success: (data: any) => util.createAction('READ_SUCCESS', data),
failure: (error: any) => util.createAction('READ_FAILURE', error)
}
};
[Generator function]
export function* fetchUsers(action) {
yield put(actions.readUsers.request());
try {
let data = {id: action.payload.id, option: action.payload.option};
let response = yield call(API().getUsers, data);
if (response) {
yield put(actions.readUsers.success(response.users));
} else {
yield put(actions.readUsers.failure(response.error));
}
} catch (error) {
yield put(actions.readUsers.failure(error));
}
};
[Unit test]
const id = 'user-id';
const option = {};
const result = {users: []};
const action = {payload : {id, option}};
describe('user-saga', () => {
describe('fetchUsers', () => {
const generator = fetchUsers(action);
it('should return actions.readUsers.request()', () => {
assert.deepEqual(generator.next().value, put(actions.readUsers.request()));
}); // PASS
it('should return the Api.getUsers call', () => {
assert.equal(generator.next(result).value, call(Api().getUsers, action.payload));
}); // FAIL
it('should return actions.readUsers.success()', () => {
assert.deepEqual(generator.next(result).value, put(actions.readUsers.success(result)));
}); // FAIL
it('should be finished', () => {
assert.deepEqual(generator.next().done, true);
}); // PASS
})
});
[Test output]
First test passed.
Second test failed with this error message
AssertionError: { '##redux-saga/IO': true,
CALL: { context: null, fn: [Function: getUsers], args: [ [Object] ] } } deepEqual { '##redux-saga/IO': true,
CALL: { context: null, fn: [Function: getUsers], args: [ [Object] ] } }
Third test failed with this error message
AssertionError: undefined deepEqual { '##redux-saga/IO': true,
PUT: { channel: null,
action: { type: 'READ_SUCCESS', users: undefined } } }

Mocha: Ensure the done() callback is being called in this test

I have written a small interceptor using axios that would flush out localBrowserStorage and would redirect user to login page if response code is 401.
It works fine but I am getting some errors in the unit test.
Test
describe('Api Service', () => {
let sandbox;
beforeEach(() => {
moxios.install();
sandbox = sinon.sandbox.create();
});
afterEach(() => {
moxios.uninstall();
sandbox.restore();
});
describe.only('interceptors', () => {
it('clear storage and redirect to login if response status code is 401', (done) => {
moxios.withMock(() => {
sandbox.spy(browserHistory, 'push');
sandbox.spy(storage, 'clear');
axios.get('/test');
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 401,
response: {}
}).then(() => {
expect(browserHistory.push).to.have.been.calledWith('/login');
expect(storage.clear).to.have.been.called; // eslint-disable-line no-unused-expressions
done();
});
});
});
});
});
});
I get these two warnings with this:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Request failed with status code 401
(node:5338) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): AssertionError: expected push to have been called with arguments /login
And this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
EDIT:
axios.interceptors.response.use((response) => {
if (response.status === 401) {
storage.clear();
browserHistory.push('/login');
return response;
}
return response;
});
You need to pass rejections back to Mocha by calling done in the catch handler:
request.respondWith({
status: 401,
response: {}
}).then(() => {
expect(browserHistory.push).to.have.been.calledWith('/login');
expect(storage.clear).to.have.been.called; // eslint-disable-line no-unused-expressions
done();
}).catch(done);
If you don't handle rejected promises, there is a chance that done never gets called (resulting in a timeout).
For instance, if one of the expectations fails, it will throw an error. When that happens, the call to done following the expectation lines will never get executed. You can (and should) catch the error with a .catch clause, as above. That should call done with the error. The code I'm using is short for:
.catch(function(err) {
done(err);
})

How to test Promise catch with Mocha

I'm trying to test the GET HTTP method from a requests module:
const get = (host, resource, options) => {
...
return new Promise((resolve, reject) => fetch(url, opts)
.then(response => {
if (response.status >= 400) {
reject({
message: `[API request error] response status: ${response.status}`,
status: response.status });
}
resolve(response.json());
})
.catch(error => reject(error)));
};
And here is how I tested the .then part:
it('Wrong request should return a 400 error ', (done) => {
let options = { <parameter>: <wrong value> };
let errorJsonResponse = {
message: '[API request error] response status: 400',
status: 400,
};
let result = {};
result = get(params.hosts.api, endPoints.PRODUCTS, options);
result
.then(function (data) {
should.fail();
done();
},
function (error) {
expect(error).to.not.be.null;
expect(error).to.not.be.undefined;
expect(error).to.be.json;
expect(error).to.be.jsonSchema(errorJsonResponse);
done();
}
);
});
However I didn't find a way to test the catch part (when it gives an error and the response status is not >= 400).
Any suggestions?
It would also help me solve the problem a simple example with another code that tests the catch part of a Promise.
I've ended up writing the following code in order to test the catch:
it('Should return an error with invalid protocol', (done) => {
const host = 'foo://<host>';
const errorMessage = 'only http(s) protocols are supported';
let result = {};
result = get(host, endPoints.PRODUCTS);
result
.then(
() => {
should.fail();
done();
},
(error) => {
expect(error).to.not.be.null;
expect(error).to.not.be.undefined;
expect(error.message).to.equal(errorMessage);
done();
}
);
});