Demonstrating a pragmatic way on how to test code paths resulting in process.exit
using the Jest test library.
Table of Contents
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 🙂