stubbing process.exit with jest

18,797

Solution 1

The other suggestions in this thread would cause errors on my end, where any tests with process.exit would run indefinitely. The following option worked for me on TypeScript, but it should work on JavaScript as well:

const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
myFunc(condition);
expect(mockExit).toHaveBeenCalledWith(ERROR_CODE);

The catch is that simply using spyOn meant that the original process.exit() function was still called, ending the process thread and hanging tests. Using mockImplementation at the end replaces the function body with the provided function (which is empty in my example).

This trick is also useful for tests that print to, say, stdout. For example:

const println = (text: string) => { process.stdout.write(text + '\n'); };
const mockStdout = jest.spyOn(process.stdout, 'write').mockImplementation(() => {});
println('This is a text.');
expect(mockStdout).toHaveBeenCalledWith('This is a text.\n');

This will let you test printed values, and have the added benefit of not messing up CLI console output with random linebreaks.


Just one note: As with any "jest.spyOn" call, regardless of using mock implementation or not, you need to restore it later on in order to avoid weird side-effects of lingering mocks. As such, remember to call the two following functions at the end of the current test case:

mockExit.mockRestore()
mockStdout.mockRestore()

Solution 2

For most of the global javascript object, I try to replace with my stub and restore after the test. Following works fine for me to mock process.

  describe('myFunc', () => {
    it('should exit process on condition match', () => {
      const realProcess = process;
      const exitMock = jest.fn();

      // We assign all properties of the "real process" to
      // our "mock" process, otherwise, if "myFunc" relied
      // on any of such properties (i.e `process.env.NODE_ENV`)
      // it would crash with an error like:
      // `TypeError: Cannot read property 'NODE_ENV' of undefined`.
      global.process = { ...realProcess, exit: exitMock };

      myFunc(true);
      expect(exitMock).toHaveBeenCalledWith(ERROR_CODE);
      global.process = realProcess;
    });
  });

This help to avoid running the real process.exit to avoid crashing the unit testing.

Solution 3

I faced a similar problem. Solved it with the code below

const setProperty = (object, property, value) => {
    const originalProperty = Object.getOwnPropertyDescriptor(object, property)
    Object.defineProperty(object, property, { value })
    return originalProperty
}

const mockExit = jest.fn()
setProperty(process, 'exit', mockExit)

expect(mockExit).toHaveBeenCalledWith('ERROR_CODE')

Solution 4

You could use jest.spyOn as this will call the original method as well:

const exit = jest.spyOn(process, 'exit');
//run your test
expect(exit).toHaveBeenCalledWith('ERROR_CODE');

Solution 5

I had a problem with mocking process.exit before importing my module. So importing before mocking worked.

const { foo } = require("my-module");

const realProcessExit = process.exit;
process.exit = jest.fn(() => { throw "mockExit"; });
afterAll(() => { process.exit = realProcessExit; });

describe("foo", () => {
    it("should exit the program", () => {
        try {
            foo();
        } catch (error) {
            expect(error).toBe("mockExit");
            expect(process.exit).toBeCalledWith(1);
        }
    });
});

(important never to return (throw) in mocked process.exit so foo doesn't continue control flow as if nothing happened)

Share:
18,797
Nick Ginanto
Author by

Nick Ginanto

Updated on July 03, 2022

Comments

  • Nick Ginanto
    Nick Ginanto about 2 years

    I have code that does something like

     function myFunc(condition){
      if(condition){
        process.exit(ERROR_CODE)
      }
     }
    

    How can I test this in Jest? Overwriting exit in process with jest.fn() and returning it back after the test doesn't work, since the process exits

  • sjmcdowall
    sjmcdowall almost 6 years
    This didn't work for me -- jest runs and then exits itself with the exit code being called..
  • sjmcdowall
    sjmcdowall almost 6 years
    It's probably some weird problem (probably mine) with Typescript, but I could not get the above to work -- kept complaining about reassigning the global.process = and the mock type not being a proper type of exit: etc. and then crashed when I ran it.. <shrug>
  • jwineman
    jwineman almost 6 years
    I was able to get it working with const exit = jest.spyOn(process, "exit").mockImplementation(number => number);
  • khpatel4991
    khpatel4991 about 5 years
    mockImplementationOnce will complain in typescript as process exit expects to never return and here we are returning {}
  • Epic Eric
    Epic Eric about 5 years
    One possibility would be throwing an error in the mock implementation, which would also have return type never: const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { throw new Error('Mock'); }); Another possibility would be forcing the return type of the spy to "void" so that Typescript will stop complaining.
  • Lazaro Fernandes Lima Suleiman
    Lazaro Fernandes Lima Suleiman over 4 years
    Nice answer! PS: There is no need to pass an arrow function for mockImplementation unless you must set a variable on test scope and so on.
  • BigFilhao
    BigFilhao over 4 years
    Typescript will not complain if you do const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { return undefined as never });
  • rose specs
    rose specs almost 3 years
    I also found this useful to mock console.error() calls in the same way!
  • rose specs
    rose specs almost 3 years
    To get around the typescript error I used .mockImplementation((() => {}) as any) . Not an ideal solution as I am just telling Typescript to ignore the type but it means your solution works for anything you want to mock.
  • Ilya Kushlianski
    Ilya Kushlianski almost 3 years
    Not to face any Typescript errors with mockImplementation, just call that function without anything, if you don't need any special logic: mockImplementation() is an alternative to jest.fn()