I work with jestand supertestto test my api endpoints and tests pass without problems; however, it jestnever finishes with the Done in X time time, which I usually saw. I saw that the flag was --watchnot used, and it is not. It seems that the connection to the server never ends, so it jestremains to be asked what will happen next. UserFactory.generate()creates fake user data using a library faker.
I find it difficult to fix this problem. I followed the recommended strategies on the help page jestwith no luck and delved into the problem tracker, but saw nothing about similar problems.
This is what I will see when I run my test suite:

As you can see, the test passes 7 passes. I was told that all tests were completed, and then morganshows POSTwhat happened. jestnever exits, so this actually does not work, because it will time out on any CI server that prohibits manual exit.
I tried to use .end() & done()as well .then() & done()instead async/await. All of them return the same final result, and if this is a problem of a promise that does not allow jest, it will fail according to an unresolved promise, so I can not say why this does not end as usual jest.
Has anyone encountered such a problem 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`, () => {
beforeAll(async () => { await User.remove(); });
test('Returns status 201 on success.', async () => {
const { status } = await mockRoute(UserFactory.generate());
expect(status).toEqual(201);
});
afterAll(async () => {
const { users } = mongoose.connection.collections;
await users.drop();
});
});
custom / routes.js
import { Router } from 'express';
import validate from 'express-validation';
import { signUp } from './controller';
import valid from './validation'
const routes = new Router();
routes.post('/user/sign-up', validate(valid.signUp), signUp);
export default routes;
custom / 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);
}
}
custom / 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;
custom / 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'));
app.use('/api/v1', [userRoutes]);
}
index.js
import express from 'express';
import {
database,
config,
middlewares,
} from './utils';
const app = express();
const MODE = process.env.NODE_ENV;
middlewares(app);
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;