Nest e2e 测试:测试运行完成后一秒 Jest 没有退出

bar*_*den 6 jestjs nestjs

我在 NestJs 微服务中执行了一些 e2E 测试(使用 Redis 作为传输器)。一切都很顺利,只是这个过程永远不会结束。

这是控制台上显示的消息

Ran all test suites.
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.
Run Code Online (Sandbox Code Playgroud)

这是我的控制器的代码

import {Body, Controller, HttpCode, Post, Query} from '@nestjs/common';
import { AppService } from './app.service';
import {Client, ClientProxy, MessagePattern, Transport} from '@nestjs/microservices';
import {AdminDto} from "./admin.dto";
import {Admin} from "./admin.interface";
import {Observable} from "rxjs";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  /** Useful for test only **/
  @Client({ transport: Transport.REDIS })
  client: ClientProxy;

  @Post()
  @HttpCode(200)
  call(@Query('command') cmd, @Body() data: any): Observable<any> {
    return this.client.send<number>({ cmd }, data);
  }
  /** End of test **/

  @MessagePattern({cmd: 'createAdmin'})
  async createClient(adminDto: AdminDto): Promise<Admin> {
    return await this.appService.create(adminDto);
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的 app.e2e-soec.ts 文件,如您所见,我关闭了 afterEach 函数上的所有连接。

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import {Transport} from "@nestjs/microservices";
import {AppModule} from "../src/app.module";
import * as mongoose from "mongoose";
import {connection} from "mongoose";

describe('AppController (e2e)', () => {
  let server;
  let app: INestApplication;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = module.createNestApplication();
    server = app.getHttpAdapter().getInstance();

    app.connectMicroservice({
      transport: Transport.REDIS,
      options: {
        url: 'redis://0.0.0.0:6379',
      },
    });
    await app.startAllMicroservicesAsync();
    await app.init();
  });
    
  it(`/POST (create admin)`, done => {
    const adminDto = {
      firstName : 'test',
      lastName: 'toto',
      password: '1234',
      email: 'test@toto.fr',
      roles: ['ROLE_ADMIN']
    };

    return request(server)
        .post('/?command=createAdmin')
        .send(adminDto)
        .end((error, response) => {
          expect(response.status).toEqual(200);
          expect(response.body).toMatchObject({
            _id: expect.any(String),
            firstName: "test",
            lastName: "toto"
          });
          done();
        });
  });

  afterEach(async done => {
    await mongoose.connection.close();
    await connection.close();
    await app.close();
    done();
  });
});
Run Code Online (Sandbox Code Playgroud)

编辑

正如杰西·卡特的建议,我添加了leaked-handles更多线索

看来是因为redis

tcp handle leaked at one of: 
    at RedisClient.Object.<anonymous>.RedisClient.create_stream (/Users/myUser/project/admin-service/node_modules/redis/index.js:195:31)
tcp stream {
  fd: 38,
  readable: true,
  writable: true,
  address: { address: '127.0.0.1', family: 'IPv4', port: 54468 },
  serverAddr: null
}
Run Code Online (Sandbox Code Playgroud)

小智 8

可能会晚一些,但您必须从模块变量中调用 close 函数,如下所示:

afterEach(async done => {
  for (const connection of connections) {
    await connection.close();
  }
  await app.close();
  await module.close(); // <-- this line
  done();
});
Run Code Online (Sandbox Code Playgroud)

这对我有用。


Jes*_*ter 1

有时,您可能会遇到 Jest 的错误,表明它没有正确退出,例如:

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.
Run Code Online (Sandbox Code Playgroud)

这通常表明确实存在问题,不应忽视。然而,接受运行的建议--detectOpenHandles并不总能提供关于问题所在的可操作的反馈。

可以使用名为leaked-handles的替代社区包,它可能有助于追查罪魁祸首。将此包安装到dev dependencies具有非退出测试的项目中,并将代码require('leaked-handles)放在测试文件的顶部。观察测试运行程序中有关泄漏的任何输出。它通常会准确地告诉您哪些端口或服务仍然处于活动状态并阻止测试关闭。

如果您捕获此输出并在此处共享它,我们将能够帮助您了解可能缺少哪些清理逻辑的更多细节。

边注:

beforeAll我通常建议您在生命周期方法中进行模块级启动和拆卸,afterAll而不是在每个测试之间进行。它可能与您的问题无关,但会对您的测试套件运行时间产生很大影响