Errorpro
Errorpro

Reputation: 2423

Jest: how to mock console when it is used by a third-party-library?

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.

Upvotes: 164

Views: 111353

Answers (4)

tanguy_k
tanguy_k

Reputation: 12293

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).

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;



You can ask Jest/Vitest to restore automatically the original state and implementation of a mock before each test thanks to restoreMocks: true instead of manually calling mySpy.mockRestore() at the end of each test.

Upvotes: 169

luis19mx
luis19mx

Reputation: 401

To stop the logs from appearing in your test execution first create a consoleMocks file and add it to the jest configuration

setupFiles: ['./test/consoleMock.js'],

In the consoleMocks file add the following:

global.console = {
  ...global.console,
  log: jest.fn(),
  warn: jest.fn(),
}

Upvotes: 0

GibboK
GibboK

Reputation: 73918

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,
    ...
}

Upvotes: 8

Andreas Köberle
Andreas Köberle

Reputation: 110922

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')

Upvotes: 203

Related Questions