Search code examples
node.jschild-process

Killing a node.js server (launched in jest's setup hook) in jest teardown


I'm using Jest as a testing library and inside its setup hook (which is executed before all my tests), I spawn a child process which launches a testing server on a certain port. The setup code basically executes an NPM command:

"run-server-test": "NODE_ENV=test SERVER_PORT=3001 node src/index.js &",
"test": "NODE_ENV=test SERVER_PORT=3001 jest --detectOpenHandles --forceExit",

And this is the setup function:

const { spawn } = require("child_process")

module.exports = async function setup() {
  return new Promise((resolve, reject) => {
    const testServerProcess = spawn("npm", ["run", "run-server-test"])

    testServerProcess.on("error", err => {
      console.log("Failed to start subprocess.", err)
      reject(err)
    })

    testServerProcess.stdout.on("data", data => {
      if (data.toString().startsWith("Apollo Server")) {
        console.log(`\nTest server running with PID ${testServerProcess.pid}`)
        resolve(true)
      }
    })

    testServerProcess.stderr.on("data", data => {
      console.error(`stderr: ${data}`)
      reject(new Error(data.toString()))
    })
  })
}

Notice that I execute the command in background with &. When Jest finishes its job, I notice with ps that its PID it's different from the one shown in the shell. Without executing it in the background, I get an extra process, the shell's one (/bin/sh). How could I get the real PID of that process? Is there a best way to kill the process launched inside that function? Thanks!


Solution

  • My current approach is the following:

    • wrap the Express bootstrap process inside a function which returns the httpServer instance used to make the server listen to requests. This is a good case for beforeAll hook.
    • use the previous reference to close the server in the afterAll.

    Another alternative (a bit tricky), could be to launch the server once at the beginning of all your tests and close it at the end:

    • directly spawn the node process instead of passing through npm
    • store the process ID in a temporary mongo's collection (but could be anything)
    • in the teardown, I retrieve the PID and then I just do process.kill(PID, "SIGTERM")