Jest does not end after successful completion of tests

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:

enter image description here

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`, () => {
  // 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();
  });
});

custom / 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;

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'));
  // 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;
+4
2

, afterAll().

, , , jest :

afterAll(async () => {
try {
  const { todos } = mongoose.connection.collections;
  // Collection is being dropped.
  await todos.drop()
  // Connection to Mongo killed.
  await mongoose.disconnect();
  // Server connection closed.
  await server.close();
} catch (error) {
  console.log(`
    You did something wrong dummy!
    ${error}
  `);
  throw error;
}
+3

Source: https://habr.com/ru/post/1677402/


All Articles