148

I am trying to mock console.warn/error but I can't. I use a third-party-library which calls console.warn inside it. I need to test was it called or wasn't. In my test case I was trying to stub console.warn but it didn't help. After that I was trying to mock console manually it didn't work out either.

console.warn = jest.fn();
testSchema('/app/components/Users/UserItem/UserItemContainer.js');
expect(console.warn).toBeCalled();

didn't work

console.warn = jest.fn();
testSchema('/app/components/Users/UserItem/UserItemContainer.js');
console.warn('error');
expect(console.warn).toBeCalled();

did work.

But I still see console.warn node_modules/babel-relay-plugin/lib/getBabelRelayPlugin.js:138 in the terminal.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Errorpro
  • 2,253
  • 2
  • 16
  • 17

3 Answers3

196

You have to use global to access objects in the global context

global.console = {warn: jest.fn()}
expect(console.warn).toBeCalled()

or use jest.spyOn added in 19.0.0

jest.spyOn(global.console, 'warn')
Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297
  • Are you sure? Still does not work. `global.console = { warn: jest.fn(), error: jest.fn(), }; testSchema('/app/components/Users/UserItem/UserItemContainer.js'); expect(global.console.warn).toBeCalled();` – Errorpro Dec 19 '16 at 15:02
  • I use it to test out logging functionality, its `expect(console.warn).toBeCalled()` to test the calling. – Andreas Köberle Dec 19 '16 at 15:07
  • 3
    Yes guys it works. But one thing is that you have to require the lib after you declare global.console. I did it wrong. I required my lib and after that declared global. Thank you. – Errorpro Dec 19 '16 at 15:14
  • Also it doesn't work when you use ES6 import feature. It only works with require. – Errorpro Dec 19 '16 at 15:16
  • 3
    Interesting how this is not documented anywhere on Jest's website. I am searching and can't find anything explaining this. – Leonardo May 23 '17 at 12:43
  • 2
    with typescript: _error TS2322: Type '{ warn: Mock<{}>; }' is not assignable to type 'Console'._ – Gerard Brull Jul 19 '17 at 12:31
  • it works with *console.warn = jest.fn();* _global.console = {warn: jest.fn()}_ – Gerard Brull Jul 19 '17 at 12:37
  • Ive updated the answer, `spyOn` should also work in typescript – Andreas Köberle Jul 19 '17 at 12:38
  • 8
    just so that everyone's aware. `global.console = {...}` will _suppress_ the error while `jest.spyOn(...)` will not. You decide if you want errors suppressed or not in your tests. – a11smiles May 02 '18 at 02:10
  • 56
    I prefer `jest.spyOn(...)` because it is easier to clean up and I'm using TypeScript but also wanted to suppress errors in the output as @a11smiles mentions. So I used `jest.spyOn(global.console, "warn").mockImplementation(() => {})` which prevents the spy calling through to the underlying `console.warn` – djskinner Jun 07 '18 at 09:18
  • @djskinner That's the best solution, as you can mock the particular method without replacing the whole thing. You might want to make that an answer... – Eric Haynes Jul 20 '18 at 22:28
  • when testing `checkPropTypes` and using `spyOn`, all tests after a failed case are also failed, but assigning `global.console` with a new `fn()` in each test worked for me, thanks! – Aprillion Sep 19 '18 at 10:59
  • how can i mock the logger.error and check different test cases , i have posted a question any help appreciated https://stackoverflow.com/questions/54250372/logger-implementation-using-winston-morgan-and-winston-daily-rotate-file – Learner Jan 20 '19 at 07:09
155

Use jest.spyOn() and mockRestore().

const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation();
...
consoleWarnMock.mockRestore();

The accepted answer does not restore the original console.warn() and will "compromise" the other tests inside the same file (if console.warn() is used inside the other tests or the code being tested).

FYI if you use console.warn = jest.fn() in a test file, it won't affect other test files (e.g console.warn will be back to its original value in the other test files).

Advice: you can call consoleWarnMock.mockRestore() inside afterEach()/afterAll() to be sure that even if a test crashes, it won't compromise the other tests from the same file (e.g ensures the tests inside the same file are fully isolated).

Full example:

const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation();
console.warn('message1'); // Won't be displayed (mocked)
console.warn('message2'); // Won't be displayed (mocked)
expect(console.warn).toHaveBeenCalledTimes(2);
expect(consoleWarnMock).toHaveBeenCalledTimes(2); // Another syntax
expect(console.warn).toHaveBeenLastCalledWith('message2');
expect(consoleWarnMock).toHaveBeenLastCalledWith('message2'); // Another syntax
expect(consoleWarnMock.mock.calls).toEqual([['message1'], ['message2']]);
expect(console.warn.mock.calls).toEqual([['message1'], ['message2']]);
consoleWarnMock.mockRestore(); // IMPORTANT
//console.warn.mockRestore(); // Another syntax

console.warn('message3'); // Will be displayed (not mocked anymore)
expect(consoleWarnMock).toHaveBeenCalledTimes(0); // Not counting anymore
expect(consoleWarnMock.mock.calls).toEqual([]);
//expect(console.warn.mock.calls).toEqual([]); // Crash

You cannot write

console.warn = jest.fn().mockImplementation();
... 
console.warn.mockRestore();

because it won't restore the original console.warn().

/!\ With mockImplementationOnce() you will still need to call consoleWarnMock.mockRestore():

// /!\
const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
console.warn('message1'); // Won't be displayed (mocked)
expect(console.warn).toHaveBeenCalledTimes(1);
expect(consoleWarnMock).toHaveBeenCalledTimes(1); // Another syntax
expect(console.warn).toHaveBeenLastCalledWith('message1');
expect(consoleWarnMock).toHaveBeenLastCalledWith('message1'); // Another syntax
expect(consoleWarnMock.mock.calls).toEqual([['message1']]);
expect(console.warn.mock.calls).toEqual([['message1']]);

console.warn('message2'); // Will be displayed (not mocked anymore)
// /!\
expect(console.warn).toHaveBeenCalledTimes(2); // BAD => still counting
expect(consoleWarnMock.mock.calls).toEqual([['message1'], ['message2']]);
expect(console.warn.mock.calls).toEqual([['message1'], ['message2']]);

consoleWarnMock.mockRestore(); // IMPORTANT
//console.warn.mockRestore(); // Another syntax
console.warn('message3'); // Will be displayed (not mocked anymore)
expect(consoleWarnMock).toHaveBeenCalledTimes(0); // Not counting anymore
expect(consoleWarnMock.mock.calls).toEqual([]);
//expect(console.warn.mock.calls).toEqual([]); // Crash

You can also write:

const assert = console.assert;
console.assert = jest.fn();
...
console.assert = assert;
tanguy_k
  • 11,307
  • 6
  • 54
  • 58
  • This solution is not working for me; jest 25.5.4. It just doesn't mock the console method. The number of calls is zero and the original method(`console.error` for me) still gets called. – cst1992 Mar 08 '21 at 09:39
  • @cst1992 hey , did you find any other way to test it , if so please let us know that would be great , i am also stuck with this issue . – SakthiSureshAnand Mar 09 '21 at 13:22
  • @SakthiSureshAnand No luck yet :( – cst1992 Mar 10 '21 at 09:43
  • @cst1992 oh no :( me too , could not find yet – SakthiSureshAnand Mar 10 '21 at 09:50
  • I was going to suggest spying on `globalThis.console` instead of just `console`, which worked for me, but now I find that plain `console` is also working for me (Jest 26.6) so maybe I'm just lucky? :( Good luck! – Mechanical Fish Mar 18 '21 at 17:09
6

You could try the following, test and pleae make sure to have clearMocks to true in your jest config file.

test('it should console warn a message', ()=>{
    jest.spyOn(global.console, 'warn').mockImplementation();

    console.warn('my error');
    expect(console.warn).toBeCalledTimes(1)
    expect(console.warn).toBeCalledWith('my error');
})
module.exports = {
    ...
    clearMocks: true,
    ...
}
GibboK
  • 71,848
  • 143
  • 435
  • 658