Start Server in Jest
Here's how to start a server in Jest.
Test Scenario
Jest is a test environment. It'll run all sorts of tests. Most commonly, you might set up a unit test. You'll import some src from your test, exercise it and make assertions on output.
But sometimes you might want to test something larger than a unit. Maybe multiple units integrating together. Or maybe interact with your code at the network level.
That's what we're going to do here: We're going to start our server that serves an HTTP API, make requests to it and then assert the response. We'll have the jest process start the API that will be under test.
Test Script
It'll be nice to have an npm-script for this, so let's add it to package.json
:
{
"test:integ": "jest --config jest.config.integ.js",
}
Configure Jest
Let's implement that jest.config.integ.js
file:
module.exports = {
globalSetup: "./test/integ/setup.js",
globalTeardown: "./test/integ/teardown.js",
testPathIgnorePatterns: ["/node_modules/", "test/unit"],
testTimeout: 5000,
};
globalSetup
will be code that runs before any test suite runs.globalTeardown
will be code that at the very end, after all test suites run.testPathIgnorePatterns
is going to make sure that this special group of tests,integ
or "integration" tests, ignore the unit tests and don't run them when using this script.testTimeout
will allow a lot of time for the tests to run. These will be longer-running than a common unit test.
Implement globalSetup
We'll write that setup.js
file now:
const { spawn } = require("child_process");
module.exports = function setup() {
return new Promise((resolve) => {
const child = spawn("node src/server.js", ["--port", "3005"], {
cwd: __dirname + "/../..",
});
globalThis.__INTEG_TEST_SERVER_PID__ = child.pid;
child.stderr.on("data", (data) => {
const str = data.toString();
console.log("[server]", str);
if (str.includes("localhost:3005")) {
setTimeout(resolve, 200);
}
});
});
};
spawn
is being used to fork a new process from node. In this case, we're starting our server withnode src/server.js
.- We're setting the directory that we're running this command using the
cwd
option. This will bring us out of ourtest/integ
directory, wheresetup.js
is located. spawn
creates a child process that has a process id, orpid
. We savve this for later, so we can stop the process.on("data")
is going to create a listener for output from the server. We want the server to be fully started before we resolve the asyncsetup
function that we're in. The way we do this for this server is read the server location log ("localhost:3005") from stderr. Your own server will likely have something different here.
Implement globalTeardown
Now how do we stop this? Let's implement teardown.js
:
module.exports = function teardown() {
process.kill(globalThis.__INTEG_TEST_SERVER_PID__);
};
Remember that pid that we saved? We can access setup.js
globals here in teardown.js
.
Start to Finish
So how does this work? We write a test. It makes HTTP requests against localhost:3005. We set up the above code to be able to start our API server. We npm run test:integ
. It'll run setup.js
, starting the server. Then our test suite will run. It'll be able to contact localhost:3005
and make the assertions in our test. We'll run all our tests in the suite. Jest will trigger teardown.js
to run. It'll kill the server process. We'll retire happy programmers.