JestJS: Async test isn't stopped - javascript

I got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
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.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}

Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.

Related

Mock implementation of another module not working in jest

I am unit testing (using jest) a function named "getTripDetails" (inside file trip.js) that calls another file "getTrace.js" from different module (which exports a function as shown below).
I want to mock the call of function "getTrace" while testing "getTripDetails" function.
file: trips.js
const gpsTrace = require("./gpsTrace");
getTripDetails = async(req, res)=>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return {status:200};
}
file: getTrace.js
module.exports = async(payload, token) =>{
try {
//code
} catch (e) {
error(e)
throw new Error(e)
}
}
This is what i tried after reading the docs.
file: test.js
let ctrl = require("./trips");
describe("API -- testing", function () {
it("Trip details", async function () {
jest.mock('./gpsTrace');
const gpsTrace = require('./gpsTrace');
gpsTrace.mockImplementation(() => {});
gpsTrace();
await ctrl.getTripDetails({},{});
expect(response.status).to.eql(200);
});
});
It did not get mocked, instead it was calling the original implementation.
Any suggesstions?
You were pretty close! Here are the updated files with comments describing the changes:
gpsTrace.js
Added a console.log message. We won't see this in the test if the mock works successfully.
module.exports = async (payload, token) => {
try {
//code
console.log("You won't see me in the Jest test because of the mock implementation")
} catch (e) {
error(e)
throw new Error(e)
}
}
trips.js
You needed to export your code to be used in other modules. Seeing as you're calling ctrl.getTripDetails() in the test, it makes sense to export your getTripDetails() on an object at the bottom of the file.
const gpsTrace = require("./gpsTrace");
const getTripDetails = async (req, res) =>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return { status:200 };
}
module.exports = {
getTripDetails,
}
gpsTrace.test.js
Make sure to import your modules at the top of the file. Remember that ctrl.getTripDetails({}, {}) calls gpsTrace internally, so no need to call it twice in your test. You also needed to save the response returned from getTripDetails into a variable to be able to compare it: const response = await ctrl.getTripDetails({}, {});.
// make sure your require statements go at the top of the module
const gpsTrace = require('./gpsTrace');
let ctrl = require("./trips");
jest.mock('./gpsTrace');
gpsTrace.mockImplementation(() => {});
describe("API -- testing", function () {
it("Trip details", async function () {
// ctrl.getTripDeals() calls your gpsTrace function internally, so no need to call it twice
// gpsTrace(); <-- can be removed
// you needed to save the returned response into a variable to be able to test it.
const response = await ctrl.getTripDetails({}, {});
expect(response.status).toEqual(200);
});
});
Result
After running the test it now successfully passes. Notice that we DO NOT see the console.log message in the gpsTrace function, which indicates our mockedImplementation of the function is working in the test script. 👍

How to unit test an Express Controller with Jest

I have been getting terribly confused with how to test my controller functions. I realize that I have to mock my dependencies, request, response, and the controller functions. Here's what I have so far:
OrdersController.js
const OrderService = require('../services/orderServices')
module.exports = class OrdersController {
static async apiGetOrders(req, res, next) {
try {
const orders = await OrderService.getOrders()
return res.status(200).json(orders)
} catch (error) {
return res.status(500).json({ error: 'Unable to get orders' }) // 500, Internal Service Error, generic
}
}
static async apiPostOrder(req, res, next) {
// All good, create an orderDocument
try {
const orderDocument = {
_id: null, // undefined at this point Mongo creates this _id for us
orderId: req.body.orderId,
cookies: req.body.cookies,
daySelected: req.body.daySelected,
timeSelected: req.body.timeSelected,
userInfo: req.body.userInfo,
createdAt: new Date(),
}
await OrderService.addOrder(orderDocument)
return res.status(201).send('success') // status OK, something was Created
} catch (error) {
return res.status(500).json({ error }) // 500, Internal Server Error
}
}
OrdersController.spec.js
import OrderService from '../services/orderServices'
import { mockOrder, mockOrders } from '../mocks/fixtures'
import OrdersController from '../controllers/ordersController'
jest.mock('../controllers/ordersController.js')
const mockRequest = () => {
return {}
}
const mockResponse = (mockOrders) => {
const res = {};
res.status = jest.fn().mockReturnValue(200);
res.json = jest.fn().mockReturnValue(mockOrders);
return res;
}
// #3 Test the OrdersControllers
// mock dependencies: req, res, and spyOn the controller functions
describe('Orders Controller', () => {
test('[Positive], should call OrderService.getOrders and receive status 200', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse(mockOrders)
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json()).toEqual(mockOrders)
})
test('[Negative], error yields status 500', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse({status: 500, error: 'Unable to get orders'})
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(500)
expect(res.json()).toEqual(error)
})
})
I'm trying to test the happy path and the negative path on the get request. I followed this expample, https://codewithhugo.com/express-request-response-mocking/, and read all of the jest docs, https://jestjs.io/docs/mock-functions. The error that I receive is:
Questions:
Am I actually writing the tests correctly?
Am I also supposed to mock the OrderService?
Should I use Sinon or is Jest more than sufficient?
I am also new to Jest and am struggling with finding documentation that goes to enough detail to suggest to me what I'm doing wrong. But in your case, I think you might be spying on the wrong thing. The OrdersController is the subject of the test, so I don't believe that you should be mocking that. Rather you should spy on OrdersController's dependency, Orderservice and mock its methods.
Also, its not clear to me why you have next in
static async apiGetOrders(req, res, next)
You don't seem to use it in the body of the method anywhere, so hanging any testing off that value, probably won't work either.

How to unit test promise rejection in React and Jest

I am trying to write a unit test for a react component. It's a fairly standard component which calls a promise-returning method and uses 'then' and 'catch' to handle the resolution. My test is trying to validate that it calls the correct method when the promise is rejected however despite following what i believe is a standard patttern, I cannot get jest to validate the call. I have listed the relevant files here and have also put up a github sample, which is linked at the bottom of the question. That sample is simply a new react app created using npx and the files below added in.
Here is my example component:
import React from 'react';
import api from '../api/ListApi';
class ListComponent extends React.Component {
constructor(props) {
super(props);
this.fetchListSuccess = this.fetchListSuccess.bind(this);
this.fetchListFailed = this.fetchListFailed.bind(this);
}
fetchList() {
api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
fetchListSuccess(response) {
console.log({response});
};
fetchListFailed(error) {
console.log({error});
};
render() {
return(<div>Some content</div>);
};
}
export default ListComponent;
Here is the api class (note, the api doesnt exist if you run the app, its just here for example):
const getList = () => fetch("http://someApiWhichDoesNotExist/GetList");
export default { getList };
And here is the test case:
import ListComponent from './ListComponent';
import api from '../api//ListApi';
describe('ListComponent > fetchList() > When the call to getList fails', () => {
it('Should call fetchListFailed with the error', async () => {
expect.hasAssertions();
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
componentInstance.fetchList();
//Assert
try {
await errorResponse;
} catch (er) {
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
}
});
});
The problem is that the test is not executing the catch block, so, in this case, the expect.hasAssertions() is failing the test. Can anyone help me understand the catch block is not executing? Wrapping the await in the try block and asserting in the catch seems to be a standard pattern in the docs but I am fairly new to Js and React and am obviously doing something wrong.
Here is the sample project on GitHub. Any help would be greatly appreciated =)
In your console:
const errorResponse = () => Promise.reject();
await errorResponse;
//() => Promise.reject()
You're awaiting a function, not the result of the call to that function. You want to:
await errorResponse();
EDIT:
In addition to that, the rest of your test is confusing. I believe you actually want to test what happens when the fetchList method of your component is called, and it fails, I assume. So you need to call it in your test, and await it's response:
Update your component's fetchList method to return the promise.
await componentInstance.fetchList() instead of await errorResponse()
Because you catch the error in fetchList you'll never enter the catch or the try...catch so your final test should look like this:
Test:
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
await componentInstance.fetchList();
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
Component:
fetchList() {
return api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
My two cents, with my own case in a React native app:
In my component I have:
const handleRedirect = React.useCallback(() => {
client.clearStore()
.then(navigation.push('SomeScreen'))
.catch(e => logger.error('error', e));
}, []);
My test is this one, where I want to test if the promise is rejected:
it('should throw an exception on clearStore rejected', async () => {
const client = {
clearStore: jest.fn()
};
const error = new Error('clearStore failed');
client.clearStore.mockReturnValue(Promise.reject(error));
expect.assertions(1);
await expect(client.clearStore())
.rejects.toEqual(Error('clearStore failed'));
});

JS Promises wait for the function and get the returned object

I'm trying to build some test helper functions that will help me to run some hooks with Mocha to test my GraphQL queries(just that you understand the context).
I want to do the following steps each time before I run the tests:
Connect to mongoDB with mongoose
Start the server (node)
Add some test data directly into the database (via mongoose models)
// this function returns back the server instance because I need it in the 'after' hook to be able to stop it after the tests execution.
export const startServer = () => {
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI_TEST);
return mongoose.connection.once('open', () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return { schema: executableSchema };
})
);
return app.listen(9000, () => {
console.log('Server started!');
});
});
};
// now here's the before hook where the server is started
let server;
before( done => {
server = startServer(done); // here I need to wait
// some other async code that inserts test data into mongo
const user = new User({email: test#test.com});
user.save().then(() => {
// saved successfully
done();
})
});
I'm quite new in the JS world, how do you manage to wait (without async await syntax) until all the promises from startServer function are resolved and you get back the server instance and just after start inserting data into the database? It's a dummy question but also some links with a better explanation of this concept that I have here would be appreciated.
LE: The entire question has reduced to the followings:
const createServer = () => {
const server = app.listen(port, () => {
//callback body here
});
};
How to convert the callback to a Promise and at the same time return a valid server reference such that in the end I can do this:
const someOtherFunction = () => {
createServer().then(myValidServerInstance => {
//do something with that instance
}
}
TLDR: Some of the things about JS Promises were not clear for me, like for example the returning of the result via resolve() method.
So the right answer looks like this:
export const startServer = () => {
mongoose.Promise = global.Promise;
// first mistake was the use of callbacks rather than Promise approach offered by mongoose.connect
return mongoose.connect(MONGO_URI_TEST).then(() => {
console.log('Connected to MongoLab TEST instance.');
return createServer();
},
err => {
console.log('Error connecting to MongoLab TEST instance:', err);
});
};
The second one was returning directly the result of listen() before the operation get finished. So I've moved the code that is starting the server in another method and wrap the result of listen() into a promise and resolve the promise only when the server actually started listening.
const createServer = () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: executableSchema,
context: { headers: req.headers},
};
})
);
return new Promise((resolve, reject) => {
const server = app.listen(9000, () => {
if (server) {
console.log('Server started on port 9000');
resolve(server);
} else {
reject();
}
});
});
};

JEST error TypeError: specificMockImpl.apply is not a function

Trying to mock one of the function with callback from api and getting error as TypeError: specificMockImpl.apply is not a function
import { IEnvironmentMap, load } from 'dotenv-extended';
import { getTokensWithAuthCode, sdk } from '../src/connection-manager';
describe('getTokensWithAuthCode function Tests', () => {
jest.useFakeTimers();
let boxConfig: IEnvironmentMap;
beforeAll(() => {
boxConfig = load({
errorOnMissing: true,
});
});
it('should reject a promise if there is wrong auth code provided', async () => {
sdk.getTokensAuthorizationCodeGrant = jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
try {
const tokens = await getTokensWithAuthCode();
} catch (error) {
expect(error).toBe('Error');
}
});
});
And my function which is trying to test is as follow:
import * as BoxSDK from 'box-node-sdk';
import { IEnvironmentMap, load } from 'dotenv-extended';
import {ITokenInfo} from '../typings/box-node-sdk';
const boxConfig: IEnvironmentMap = load({
errorOnMissing: true,
});
export const sdk: BoxSDK = new BoxSDK({
clientID: boxConfig.BOX_CLIENT_ID,
clientSecret: boxConfig.BOX_CLIENT_SECRET,
});
/**
* - Use the provided AUTH_CODE to get the tokens (access + refresh)
* - Handle saving to local file if no external storage is provided.
*/
export async function getTokensWithAuthCode() {
return new Promise((resolve: (tokenInfo: ITokenInfo) => void, reject: (err: Error) => void) => {
if (boxConfig.BOX_AUTH_CODE === '') {
reject(new Error('No Auth Code provided. Please provide auth code as env variable.'));
}
sdk.getTokensAuthorizationCodeGrant(boxConfig.BOX_AUTH_CODE, null, (err: Error, tokenInfo: ITokenInfo) => {
if (err !== null) {
reject(err);
}
resolve(tokenInfo);
});
});
}
Is there any other way to mock function in jest? I have read an article https://www.zhubert.com/blog/2017/04/12/testing-with-jest/
On this line, rather than pass a function to mockImplementation, you're passing three arguments:
jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
It looks like you might have just missed some braces. Try switching it to:
jest.fn().mockImplementation((boxConfig.BOX_AUTH_CODE, null, cb) => {
cb('Error', null);
});
It's better not trying to mutate a const used elsewhere.
You could change getTokensWithAuthCode to make it receive sdk as parameter, thus in your test you would pass the mock function as argument, therefore having a more predictable behavior than mutating directly sdk.
In your code, you could make a second getTokensWithAuthCode implementation, with the signature getTokensWithAuthCodeUnbound(sdk) for example, and export it. This implementation will be used in your tests.
Exporting using the same getTokensWithAuthCode name, you would call:
export const getTokensWithAuthCode = getTokensWithAuthCodeUnbound.bind(null, sdk)
That way, your app will use getTokensWithAuthCodeUnbound bound with the default sdk, and you can test more easily its implementation.
Mozilla Developer Network (MDN) bind documentation.

Categories