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,
};
globalSetupwill be code that runs before any test suite runs.globalTeardownwill be code that at the very end, after all test suites run.testPathIgnorePatternsis going to make sure that this special group of tests,integor "integration" tests, ignore the unit tests and don't run them when using this script.testTimeoutwill 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);
}
});
});
};
spawnis 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
cwdoption. This will bring us out of ourtest/integdirectory, wheresetup.jsis located. spawncreates 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 asyncsetupfunction 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.