0

I have a simple Express application that exposes a RESTful API. It uses Knex and Objection to access the database, and Jest / Supertest for testing the API.

I have a test that starts the server, fetches the available data from a given route and asserts the received values. Everything works fine, except that Jest never exits after executing this test.

This is my test:

import { app } from "../../src/application.js";

import { describe, expect, test } from "@jest/globals";
import request from "supertest";

describe("Customer Handler", () => {
  test("should retrieve all existing customer(s)", async () => {
    const expected = [
       // ...real values here; omitted for brevity
    ];
    const response = await request(app).get(`/api/customers`);
    expect(response.statusCode).toStrictEqual(200);
    expect(response.headers["content-type"]).toContain("application/json");
    expect(response.body).toStrictEqual(expected);
  });
});

The application.js file looks very much like a usual Express setup/configuration file:

import { CustomerHandler } from "./handler/customer.handler.js";
import connection from "../knexfile.js";

import express from "express";
import Knex from "knex";
import { Model } from "objection";

const app = express();

Model.knex(Knex(connection[process.env.NODE_ENV]));

// ...removed helmet and some other config for brevity
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api/customers", CustomerHandler);
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.json({
    errors: {
      error: err,
      message: err.message,
    },
  });
  next();
});

export { app };

I've tried --detectOpenHandles, but nothing else is printed in the console, so I can't see any hints about what the issue might be — which I suspect it's Knex / Objection because I'm using SQLite, so probably the connection to the database is not getting closed.

In the meantime I'm using --forceExit, but I would like to figure it out why is Jest not able to exit.

x80486
  • 6,627
  • 5
  • 52
  • 111
  • You can try adding a parameter `done` to the function you pass as a second argument to `test`. Then, call `done()` when you are done the test. – zr0gravity7 Jul 26 '21 at 14:24
  • 1
    I've tried this, but I'm getting this error message: `Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise. Returned value: Promise {}`. Basically, I just do `... async (done) => { ...`, and call `done()` in the very end of the `test(...) {}` block. – x80486 Jul 26 '21 at 14:28

1 Answers1

1

OK, looks like I was on the right track for this one.

Refactoring application.js to export the Knex object (after it's configured), and calling knex.destroy() after the test passes is the solution for this setup.

// application.js

...

const knex = Knex(connection[process.env.NODE_ENV]);

Model.knex(knex);

...

export { app, knex };

...then in the test(s), make sure you import knex and call destroy():

// customer.handler.test.js
import { app, knex } from "../../src/application.js";

import { afterAll, describe, expect, test } from "@jest/globals";
import request from "supertest";

describe("Customer Handler", () => {
  afterAll(async () => {
    await knex.destroy();
  });

  test("should retrieve all existing customer(s)", async () => {
    const expected = [ /* ...real values here */ ];
    ...
    expect(response.body).toStrictEqual(expected);
  });
});
x80486
  • 6,627
  • 5
  • 52
  • 111