121

I'm using create-react-app and trying to write a jest test that checks the output of a console.log.

My function to test is:

export const log = logMsg => console.log(logMsg);

My test is :

it('console.log the text "hello"', () => {
  console.log = jest.fn('hello');
  expect(logMsg).toBe('hello');
});

Here is my error

 FAIL  src/utils/general.test.js
  ● console.log the text hello

    expect(received).toBe(expected)    Expected value to be (using ===):      "hello"
    Received:
      undefined
    Difference:
      Comparing two different types of values. Expected string but received undefined.
norbitrial
  • 14,716
  • 7
  • 32
  • 59
Hello-World
  • 9,277
  • 23
  • 88
  • 154

4 Answers4

132

If you want to check that console.log received the right parameter (the one that you passed in) you should check mock of your jest.fn().
You also have to invoke your log function, otherwise console.log is never invoked:

it('console.log the text "hello"', () => {
  console.log = jest.fn();
  log('hello');
  // The first argument of the first call to the function was 'hello'
  expect(console.log.mock.calls[0][0]).toBe('hello');
});

or

it('console.log the text "hello"', () => {
  console.log = jest.fn();
  log('hello');
  // The first argument of the first call to the function was 'hello'
  expect(console.log).toHaveBeenCalledWith('hello');
});

If you're going with this approach don't forget to restore the original value of console.log.

Another option is to use jest.spyOn (instead of replacing the console.log it will create a proxy to it):

it('console.log the text "hello"', () => {
  const logSpy = jest.spyOn(console, 'log');

  console.log('hello');

  expect(logSpy).toHaveBeenCalledWith('hello');
});

Read more here.

JeB
  • 11,653
  • 10
  • 58
  • 87
  • FAIL src/utils/general.test.js ● console.log the text "hello" TypeError: specificMockImpl.apply is not a function at CustomConsole.mockConstructor [as log] (node_modules/jest-mock/build/index.js:288:37) at Object..exports.logger.logMsg (src/utils/general.js:13:51) at Object..it (src/utils/general.test.js:16:23) at new Promise () at Promise.resolve.then.el (node_modules/p-map/index.js:46:16) at – Hello-World Mar 04 '18 at 14:31
  • My mistake. `jest.fn('hello')` should be `jest.fn()`. Try now. Also, if it answers your question you can accept it as a right answer. – JeB Mar 04 '18 at 15:16
  • Thank good answer. You wrote `log('hello')` in the first two examples. Did you mean `console.log('hello')`? – Mir-Ismaili Jul 29 '22 at 06:36
  • 1
    @Mir-Ismaili No, I actually meant `log`. `log` is your function which calls to `console.log`. You want to check that it works properly and therefore replace `console.log` with a mock. Then you call your `log` function (which is supposed to call `console.log`) and make sure that the mock was indeed called. – JeB Jul 29 '22 at 11:12
83

Or you could do it like this:

it('calls console.log with "hello"', () => {
  const consoleSpy = jest.spyOn(console, 'log');

  console.log('hello');

  expect(consoleSpy).toHaveBeenCalledWith('hello');
});
Dozatron
  • 1,056
  • 9
  • 7
  • 3
    This is the safest and least side-effect answer, I recommend it over other solutions. You can use the spy to mute the default behavior as well and jest will ensure everything is restored correctly at the end of the test (unlike most of these other answers). It's also the most concise and compositional approach. – ProLoser Dec 10 '21 at 18:22
  • 1
    To clarify for others what @ProLoser was suggesting, you can use `jest.spyOn(console, 'log').mockImplementation(() => {})` in order to prevent the log from cluttering your test logs, if you want. – blwinters Mar 22 '23 at 16:25
17

Another option is to save off a reference to the original log, replace with a jest mock for each test, and restore after all the tests have finished. This has a slight benefit to not polluting the test output and still being able to use the original log method for debugging purposes.

describe("Some behavior that will log", () => {
  const log = console.log; // save original console.log function
  beforeEach(() => {
    console.log = jest.fn(); // create a new mock function for each test
  });
  afterAll(() => {
    console.log = log; // restore original console.log after all tests
  });
  test("no log", () => {
    // TODO: test something that should not log
    expect(console.log).not.toHaveBeenCalled();
  });
  test("some log", () => {
    // TODO: execute something that should log
    expect(console.log).toHaveBeenCalledWith(
      expect.stringContaining("something")
    );
    const message = console.log.mock.calls[0][0]; // get actual log message
    log(message); // actually log out what the mock was called with
  });
});

filoxo
  • 8,132
  • 3
  • 33
  • 38
  • 2
    the only solution that works in isolated tests. this should be the accepted answer, as other solutions would give a false negative response on things that have already been logged – Coco Liliace Jun 25 '21 at 21:20
3

I would consider toHaveBeenCalledWith or any other of the methods that jest offers for checking mock calls (the ones that start with toHaveBeenCalled).

it('console.log the text "hello"', () => {
  console.log = jest.fn();
  log('hello');
  expect(console.log).toHaveBeenCalledWith('hello');
});
Bill
  • 3,478
  • 23
  • 42
Simply007
  • 444
  • 3
  • 14
  • is this isolated to that specific test? – Colin D Mar 15 '20 at 01:06
  • 2
    You could call [`mockReset()` od `mockClear()`](https://jestjs.io/docs/en/mock-function-api.html#mockfnmockreset) to reset concole.log mocking. – Simply007 Apr 11 '20 at 14:19
  • hmmm. it just concerns me that a statement like this would have global side effects. I would like to only mock console in a test that i know is going to log. and then that combined with the fact that tests are run in parallel? it seems like it is not sufficient to reset logs if it is doing global side effects since tests run in parallel – Colin D Apr 11 '20 at 16:50