Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fastify, jest: running processes after calling .close() on fastify instance

I have the following function for opening a database connection and running my fastify server instance (server.js)

import fastify from "fastify";
import fastifyCors from "fastify-cors";
import { MongoClient } from "mongodb";

import characterRoute from "./routes/character";
import gearRoute from "./routes/gear";

import CharacterDAO from "./dao/character";
import GearDAO from "./dao/gear";

import { seedData } from "./dataSeed";

let connection;
let serverInstance;

// server startup command
export const startServer = async port => {
  try {
    serverInstance = fastify({ logger: false });
    serverInstance.register(fastifyCors, {
      origin: "*"
    });
    serverInstance.register(characterRoute);
    serverInstance.register(gearRoute);

    await serverInstance.listen(port, "0.0.0.0");
  } catch (err) {
    serverInstance.log.error(err);
    process.exit(1);
  }
};

// server shutdown command
export const killServer = async () => {
  await serverInstance.close();
  console.log("server instance closed");
  await connection.close();
  console.log("db connection closed");
};

// connect mongoDb, seed data if needed, run fastify server
export const runServer = async ({ dbUrl, dbName, environment, port }) => {
  // test seed data when starting server if running a test suite
  if (environment === "test") {
    await seedData({
      hostUrl: dbUrl,
      databaseName: dbName
    });
  }

  connection = await MongoClient.connect(dbUrl, {
    poolSize: 50,
    useNewUrlParser: true,
    useUnifiedTopology: true,
    wtimeout: 2500
  });
  const database = await connection.db(dbName);
  // inject database connection into DAO objects
  CharacterDAO.injectDB(database);
  GearDAO.injectDB(database);
  // start the fastify server
  await startServer(port);
};

Now I would like to run some tests against a running server instance and then exit the runner. I've been reading the fastify docs and tried to adapt the setup to jest. This is my test file:

import { killServer, runServer } from "../server";

const serverOptions = {
  dbUrl: process.env.dbUrl,
  dbName: process.env.dbName,
  environment: process.env.environment,
  port: process.env.port
};

beforeAll(async () => {
  // run the server instance we are testing against
  await runServer({
    ...serverOptions
  });
});

afterAll(async () => {
  // figure out a way to kill the test server here
  await killServer();
});

But after my tests complete I get the following warning in the console:

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.

And I have to end the test suite manually.

What could be the cause of this? I have the .close() function seems to get called correctly after the test suites are done (I get the console.logs after).

For reference, jestconfig.json

{
  "transform": {
    "^.+\\.(t|j)sx?$": "babel-jest"
  },
  "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}

The npm test script (parameters are omitted)

dbUrl=mongodb+srv://[email protected]  dbName=dsa-fwa- environment=test port=4000 npx jest --config jestconfig.json

Running the same script with --detectOpenHandles does not provide any additional output.

like image 647
Miha Šušteršič Avatar asked Feb 25 '20 16:02

Miha Šušteršič


1 Answers

For anyone finding this, the solution was to refactor server.js and do a more detailed tests setup:

server.js

export const buildFastify = () => {
  const serverInstance = fastify({ logger: false });
  serverInstance.register(fastifyCors, {
    origin: "*"
  });
  serverInstance.register(characterRoute);
  serverInstance.register(gearRoute);
  return serverInstance;
};

export const buildDbConnection = async ({ dbUrl }) => {
  const connection = await MongoClient.connect(dbUrl, {
    poolSize: 50,
    useNewUrlParser: true,
    useUnifiedTopology: true,
    wtimeout: 2500
  });
  return connection;
};

.test.js

const supertest = require("supertest");

import CharacterDAO from "../dao/character";
import GearDAO from "../dao/gear";

import { buildDbConnection, buildFastify } from "../server";

let connection;
let database;
let server;

const serverOptions = {
  dbUrl: process.env.dbUrl,
  dbName: process.env.dbName,
  environment: process.env.environment,
  port: process.env.port
};

// supertest instance to make server requests
const supertestInstance = supertest("http://localhost:4000");

beforeAll(async () => {
  // database connection to validate that response data comes from the database
  connection = await buildDbConnection({ dbUrl: serverOptions.dbUrl });
  database = await connection.db(serverOptions.dbName);

  // inject db into our DAO classes needed for the server to function properly
  CharacterDAO.injectDB(database);
  GearDAO.injectDB(database);

  // run the server instance we are testing against
  server = await buildFastify();
  await server.listen(serverOptions.port, "0.0.0.0");
});

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

So the issue was in the convoluted runServer function - calling that left a database handle open after all the tests were done.

like image 190
Miha Šušteršič Avatar answered Sep 26 '22 09:09

Miha Šušteršič