trashpanda.cc

Kohlenstoff-basiert Zweibeiniges Säugetier

Running Jest tests alongside an Express server

Here's the scenario: you've got an Express project that you're testing with Jest. You've got the server running, fire off your test suite, and you hit this error:

listen EADDRINUSE: address already in use :::3000

The root cause is that your test suite is trying to start a server instance on the default port, and failing because there's already a server running on that port.

The workaround is to persuade Jest to start its server on a different port so that it can co-exist with the production server.

That needs a bit of adaption in the server, the test suite and package.json.

1. Updating the server code

Start by defining a const for the port number:

dotenv.config();
const port = process.env.PORT || 3000;

Now wrap the server in a startServer function:

export const app: Express = express();

export function startServer(port: any) {
  return app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
  });
}

And start the server, but only if we're not in the test environment :

if (process.env.NODE_ENV !== 'test') {
  startServer(port);
}

2. Updating the test suite

Add a beforeAll() step to start the server on a testing port:

let server: any;
const testPort = 4000;

beforeAll((done) => {
  // Start server on non-standard port
  server = startServer(testPort);
  done();
});

and an afterAll() step to tear down the server after the tests have run:

afterAll((done) => {
  // Clean up all nocks
  server.close(done);
});

3. Updating package.json

Specify the Node environment in the test command:

"scripts": {
    "server": "tsc && node ./dist/server.js",
    "build": "tsc",
    "dev": "NODE_ENV=dev node --env-file=.env --watch -r ts-node/register src/server.ts",
    "test": "NODE_ENV=test jest --verbose --detectOpenHandles",
    "test:watch": "NODE_ENV=test jest --watch --verbose",
    "lint": "eslint 'src/**/*.{js,ts}'"
  },

This sets the NODE_ENV var in the environment spawned by the test process, which in turn forces the server to start on a non-production port.

Summary

The complete files should look something like this:

server.ts

import dotenv from 'dotenv';
import express, { Express, Request, Response } from "express";

dotenv.config();

const port = process.env.PORT || 3000;

export const app: Express = express();

export function startServer(port: any) {
  return app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
  });
}

if (process.env.NODE_ENV !== 'test') {
  startServer(port);
}

// ROUTES GO HERE

server.test.ts

import { app, startServer } from '../src/server';

let server: any;
const testPort = 4000;

beforeAll((done) => {
  // Start server on non-standard port
  server = startServer(testPort);
  done();
});

afterAll((done) => {
  server.close(done);
});

describe('The tests...', () => {
  // TESTS GO HERE
});

package.json

{
  ...
  "scripts": {
    "server": "tsc && node ./dist/server.js",
    "build": "tsc",
    "dev": "NODE_ENV=development node --env-file=.env --watch -r ts-node/register src/server.ts",
    "test": "jest --verbose --detectOpenHandles",
    "test:watch": "NODE_ENV=test jest --watch --verbose",
    "lint": "eslint 'src/**/*.{js,ts}'"
  },
  ...
}