skip to Main Content

I perfome some e2E test in nestJs microservice (using Redis as transporter).
Everything is going well except that the process never end.

This is the message displayed on console

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.

Here is the code of my controller

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);
  }
}

And this is my app.e2e-soec.ts file, as you can see, I close all connection on afterEach fuction.

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: '[email protected]',
      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();
  });
});

EDIT

As Jesse Carter advice, I’ve added leaked-handles to get more clues

Seems it’s because of 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
}

4

Answers


  1. Occasionally you might run into an error from Jest indicating that it did not exit properly, eg:

    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.
    

    This is usually a sign that there is indeed a problem and shouldn’t be ignored. However, taking the advice of running with --detectOpenHandles doesn’t always give actionable feedback about where the problem lies.

    An alternative community package called leaked-handles is available which might help track down the culprit. Install this package to your dev dependencies for the project that has non-exiting tests and put the code require('leaked-handles) at the top of the test files. Observe any output regarding leaks in your test runner. It will often tell you exactly what port or service is still active and preventing the test from closing.

    If you capture this output and share it here we will be able to help you with more specifics about what cleanup logic may be missing.

    Side Note:

    I would generally advise that you do module level startup and tear down in the beforeAll and afterAll lifecycle methods as opposed to doing in between each test. It might not be related to your issues but will make a big difference in how long your test suites take to run

    Login or Signup to reply.
  2. I also have this issue with Redis. Seeing TCPSERVERWRAP when using the --detectOpenHandles flag with Jest.

    As a workaround, you can pass the --forceExit flag to Jest.

    Login or Signup to reply.
  3. It might be late but you have to call the close function from the module variable, like this:

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

    Thats what worked for me.

    Login or Signup to reply.
  4. It happens to me becuase of TypeOrm connection is not getting closed after finishing the Test suite. In order to resolve the issue,

    Option 1

    1. Set TypeOrmModuleOption -> keepConnectionAlive: false

    2. Close the app at the end of the test suite.

      afterAll(async () => {
          await app.close();
      });
      

    Option 2
    Close the connection inside afterAll()

    import { Connection } from "typeorm";
    ...
    ...
    
    let app: INestApplication;
    let module: TestingModule;
    let connection : Connection;
    
    beforeAll(async () => {
        module = await Test.createTestingModule({
          imports: [
            TypeOrmModule.forRootAsync(...),
            TypeOrmModule.forFeature([DomainRepository]),
          ],
          controllers: [DomainController],
          providers: [DomainService]
        }).compile();
    
        app = module.createNestApplication();
        connection =  module.get<Connection>(Connection);
    })
    
    afterAll(async () => {
        await connection.close();
        await app.close();
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search