Testing process.exit with Jest in NodeJS

Demonstrating a pragmatic way on how to test code paths resulting in process.exit using the Jest test library.

The test scenario

With NodeJS, Jest is a very popular and powerful testing library. Consider you want to test the following function in your project…

function myFunc() {
  //
  // ...do "stuff"
  //
  if (condition) {
      process.exit(ERROR_CODE);
  }
  //
  // ...do "other stuff"
  //
 }

To reach a full test coverage, you’ll need to set up a test for the branch where condition is true. Setting up such a test in Jest without any precautions would result in a real exit of the test process before it is finished wich would cause a failed test.

Mocking process.exit

To safely test the branch where condition is true you have to mock process.exit. Of course this mocking should have been done in a way that the following code “other stuff” is never executed like if the original process.exit would kick in.

To achieve that, we use Jest’s spyOn to implement a mock for process.exit. The mock method introduced with mockImplementation will throw an exception and replace the original implementation which was exiting the entire process. The mock function will receive a number (the error code) as argument like the original exit function.

const mockExit = jest.spyOn(process, 'exit')
  .mockImplementation((number) => { throw new Error('process.exit: ' + number); });

This mock ensures that the execution of our test function ends immediately without doing “other stuff” and without ending the Jest test process. Also, this mock serves to check if process.exit was really called and what the exit code was. We do this with Jest’s toHaveBeenCalledWith test function.

Putting it all together

To get the test case finally up and running, we have to wrap our function execution in an expect( ... ).toThrow() statement because it is now throwing an exception in the mock implementation. Also, it is a good practice to restore the original mocked function by calling mockRestore to avoid unintended side-effects.

Assuming we want to test a process exit code of -1, our final test case would look like this…

it('tests myFunc with process.exit', async () => {
  const mockExit = jest.spyOn(process, 'exit')
      .mockImplementation((number) => { throw new Error('process.exit: ' + number); });
  expect(() => {
      myFunc(true);
  }).toThrow();
  expect(mockExit).toHaveBeenCalledWith(-1);
  mockExit.mockRestore();
});

The complete example code is available in a GitHub repo.

Happy coding 🙂

Useful links