I'm working with jest
& supertest
to test my api endpoints and the tests are passing with no problems; however jest
is never exiting with the Done in X amount of time statement that I would normally see. I looked to make sure the --watch
flag was not being used and it was not. It seems as if the connection to the server is never ending so jest
is left wondering what is next. UserFactory.generate()
creates fake user data using the faker
library.
I'm at a loss on trying to troubleshoot this. I've followed the recommended strategies on jest
's help page with no luck and been digging around in the issue tracker as well but see nothing about similar issues.
This is what I will see on running my test suite:
As you can see the test is ran 7 passes. I'm told that all tests have been run and then morgan
shows the POST
that has happened. jest
never exits so in reality this is failing because it will timeout on any CI sever barring a manual exit.
I have tried using .end() & done()
as well as .then() & done()
in place of the async/await
. They all are returning the same end result and if it were an issue of a promise not resolving jest
will error out over an unresolved promise so I am beyond stumped on why this is not terminating as jest
normally would.
Has anyone encountered an issue like this before?
user.controller.test.js
import mongoose from 'mongoose';
import request from 'supertest';
import { UserFactory } from '../../__mocks__';
import { User } from '../../modules';
import { config } from '../../utils';
import app from '../../';
const mockRoute = data => request(app).post(`${config.ENDPOINT}/user/sign-up`).send(data);
describe(`POST: /user/sign-up`, () => {
// remove any user data from db prior to running tests.
beforeAll(async () => { await User.remove(); });
test('Returns status 201 on success.', async () => {
// Returns the response object: res.status === { status }
const { status } = await mockRoute(UserFactory.generate());
expect(status).toEqual(201);
});
afterAll(async () => {
// drop connection to the collection
const { users } = mongoose.connection.collections;
await users.drop();
});
});
user/routes.js
import { Router } from 'express';
import validate from 'express-validation';
import { signUp } from './controller';
import valid from './validation'
const routes = new Router();
/**
* 1. Define the route: 'user/signup'.
* 2. Validate the data being provided on the POST
* against valid.signUp object.
* 3. Provide data to signUp controller method for
* creating a user in the database.
*/
routes.post('/user/sign-up', validate(valid.signUp), signUp);
export default routes;
user/controller.js
import HTTPStatus from 'http-status';
import User from './model';
import { config, filterBody } from '../../utils';
export const signUp = async (req, res, next) => {
const filteredBody = filterBody(req.body, config.WHITELIST.users.signUp);
try {
const user = await User.create(filteredBody);
return res.status(HTTPStatus.CREATED).json(user.toAuthJSON());
} catch (e) {
e.status = HTTPStatus.BAD_REQUEST;
return next(e);
}
}
user/model.js
import mongoose, { Schema } from 'mongoose';
import uniqueValidator from 'mongoose-unique-validator';
import { hashSync, compareSync } from 'bcrypt-nodejs';
import jwt from 'jsonwebtoken';
import { config } from '../../utils';
const UserSchema = new Schema({
email: {
type: String,
unique: true,
required: [true, 'Email is required!'],
trim: true,
validate: {
validator(email) {
const emailRegex = /^[-a-z0-9%S_+]+(\.[-a-z0-9%S_+]+)*@(?:[a-z0-9-]{1,63}\.){1,125}[a-z]{2,63}$/i;
return emailRegex.test(email);
},
message: '{VALUE} is not a valid email!',
}
},
password: {
type: String,
required: [true, 'Password is required!'],
trim: true,
minlength: [6, 'Password need to be longer!'],
validate: {
validator(password) {
return password.length >= 6 && password.match(/\d+/g);
},
},
}
}, { timestamps: true })
UserSchema.plugin(uniqueValidator, {
message: '{VALUE} already taken!',
});
UserSchema.pre('save', function(next) {
if (this.isModified('password')) {
this.password = this._hashPassword(this.password);
return next();
}
return next();
});
UserSchema.methods = {
authenticateUser(password) {
return compareSync(password, this.password);
},
_hashPassword(password) {
return hashSync(password);
},
createToken() {
return jwt.sign({ _id: this._id }, config.JWT_SECRET);
},
toAuthJSON() {
return {
_id: this._id,
token: `JWT ${this.createToken()}`,
};
},
toJSON() {
return {
_id: this._id,
username: this.username,
};
},
};
let User;
try {
User = mongoose.model('User');
} catch (e) {
User = mongoose.model('User', UserSchema);
}
export default User;
user/validation.js
import Joi from 'joi';
export default {
signUp: {
body: {
email: Joi.string().email().required(),
password: Joi.string()
.min(6)
.regex(/^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$/)
.required(),
},
},
};
middlewares.js
import bodyParser from 'body-parser';
import compression from 'compression';
import cors from 'cors';
import morgan from 'morgan';
import { userRoutes } from '../modules';
export default app => {
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(morgan('dev'));
// applying api routes last.
app.use('/api/v1', [userRoutes]);
}
index.js
import express from 'express';
import {
database,
config,
middlewares,
} from './utils';
// Create instance of Express.
const app = express();
const MODE = process.env.NODE_ENV;
// Apply middlewares to Express.
middlewares(app);
// Connect to the environment determined database.
database(config.MONGO_URI)
app.listen(config.PORT, err => {
if (err) { return console.error(err); }
console.log(`App running on port: ${config.PORT} in ${MODE} mode.`);
});
export default app;
unlock()
the modules here. It was just an issue of the database connection still being open and preventingjest
from exiting with 0. – Saez