测试成功完成后开玩笑没有终止

Posted

技术标签:

【中文标题】测试成功完成后开玩笑没有终止【英文标题】:Jest not terminating after tests complete successfully 【发布时间】:2017-10-17 14:02:52 【问题描述】:

我正在使用jest & supertest 来测试我的 api 端点,并且测试顺利通过;但是jest 永远不会退出我通常会看到的 Done in X amount of time 语句。我查看以确保没有使用 --watch 标志,但事实并非如此。似乎与服务器的连接永远不会结束,所以jest 不知道接下来会发生什么。 UserFactory.generate() 使用 faker 库创建虚假用户数据。

我在尝试解决此问题时不知所措。我在jest 的帮助页面上遵循了推荐的策略,但没有运气,也一直在问题跟踪器中进行挖掘,但对类似问题一无所知。

这是我在运行我的测试套件时会看到的:

如您所见,测试运行了 7 次。我被告知所有测试都已运行,然后morgan 显示已发生的POSTjest 从不退出,所以实际上这是失败的,因为它会在任何 CI 服务器上超时,除非手动退出。

我尝试使用.end() & done().then() & done() 代替async/await。他们都返回相同的最终结果,如果这是一个未解决的承诺问题 jest 将在未解决的承诺上出错,所以我很难理解为什么这不会像 jest 通常那样终止。

以前有人遇到过这样的问题吗?

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;

【问题讨论】:

【参考方案1】:

发现问题在于我在 afterAll() 挂钩中做得不够。

我创建了一个小型 repo 来重现问题并从那里解决问题,这是需要做的,以便 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;

【讨论】:

我还想补充一点,这里不需要unlock() 模块。这只是数据库连接仍处于打开状态并阻止 jest 以 0 退出的问题。 对我来说 await mongoose.disconnect(); 就足够了。我没有运行server.close();。此外,如果其他人(比如我)有多个连接(使用createConnection() 而不是connect() 打开),您只需要一个mongoose.disconnect(); 而不是connection1.disconnect()conneciton2.disconnect()【参考方案2】:

这对我有用:

afterAll(async () => 
    try 
      // Connection to Mongo killed.
      await mongoose.disconnect();
     catch (error) 
      console.log(`
        You did something wrong dummy!
        $error
      `);
      throw error;
    
);

【讨论】:

以上是关于测试成功完成后开玩笑没有终止的主要内容,如果未能解决你的问题,请参考以下文章

为啥shell脚本exit后,当前进程没有终止?

应用程序终止而没有错误

CyclicBarrier:导致屏障跳闸“y”的“x”个线程完成它们的执行并终止

如何在特定时间后终止 JFrame?

成功识别脚本中的 PID 后,如何终止进程? [复制]

成功连接几分钟后,Worklight Native iOS App 以 NSRangeException 终止