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}'"
},
...
}