238

I wonder if there is a better way to disable console errors inside a specific Jest test (i.e. restore the original console before/after each test).

Here is my current approach:

describe("Some description", () => {
  let consoleSpy;

  beforeEach(() => {
    if (typeof consoleSpy === "function") {
      consoleSpy.mockRestore();
    }
  });

  test("Some test that should not output errors to jest console", () => {
    expect.assertions(2);

    consoleSpy = jest.spyOn(console, "error").mockImplementation();
 
    // some function that uses console error
    expect(someFunction).toBe("X");
    expect(consoleSpy).toHaveBeenCalled();
  });

  test("Test that has console available", () => {
    // shows up during jest watch test, just as intended
    console.error("test");
  });
});

Is there a cleaner way of accomplishing the same thing? I would like to avoid spyOn, but mockRestore only seems to work with it.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Apidcloud
  • 3,558
  • 2
  • 21
  • 23
  • I accidentally ended up hiding a real error doing this. Ideally, first thing you should try to do is diagnose a warning or error. If it's _truly_ benign, there are plenty of answers below to help in hiding it. – Devin Rhode Sep 06 '21 at 03:22
  • @DevinRhode, it's decent advice But there are cases where console output is expected as part of a test, e.g. testing the error-handling code in a function. And sometimes you may want to call 3rd-party code that console logs, rather than mocking it out. – Erik Hermansen Jan 08 '22 at 23:02

12 Answers12

330

For particular spec file, Andreas's is good enough. Below setup will suppress console.log statements for all test suites,

jest --silent

(or)

To customize warn, info and debug you can use below setup

tests/setup.js or jest-preload.js configured in setupFilesAfterEnv

global.console = {
  ...console,
  // uncomment to ignore a specific log level
  log: jest.fn(),
  debug: jest.fn(),
  info: jest.fn(),
  // warn: jest.fn(),
  // error: jest.fn(),
};

jest.config.js

module.exports = {
    verbose: true,
    setupFilesAfterEnv: ["<rootDir>/__tests__/setup.js"],
};
dialex
  • 2,706
  • 8
  • 44
  • 74
Raja Jaganathan
  • 33,099
  • 4
  • 30
  • 33
  • 2
    Hi! `setupTestFrameworkScriptFile` is deprecated in favor of `setupFilesAfterEnv`. – elhoucine Jan 25 '19 at 17:02
  • 3
    Mocking `global.console` is indeed a simple way to go, and can be done through any configured `setupFilesAfterEnv `. Beware to mock all native methods of the `console` object or you may encounter other unexpected errors. – Vadorequest Aug 06 '19 at 14:17
  • Note that if you then want to check on the mock (or clear it) you will need to refer to it as console.log, e.g., expect(console.log).toBeCalledTimes(1). – Peter Gerdes Jun 22 '21 at 13:59
  • 2
    You can also add the `"silent": true` option to the `jest.config.js` file – Finesse Nov 29 '21 at 07:53
182

If you want to do it just for a specific test:

beforeEach(() => {
  jest.spyOn(console, 'warn').mockImplementation(() => {});
});
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Constantin
  • 3,655
  • 1
  • 14
  • 23
  • 7
    It doesn't work in my tests, I still have some `console.warn` during test. Tested multiple times, it's not bulletproof – Dimitri Kopriwa Mar 15 '21 at 12:06
  • This is a good solution. It allows me to continue looking at other console.warn (or console.log) for debugging. – Brian Ho Mar 26 '21 at 03:42
  • It doesn't work. It may have worked in the past, but now it's broken. – adi518 May 08 '21 at 14:44
  • It works, though perhaps not as you want depending on where you put the statements (see https://jestjs.io/docs/setup-teardown#order-of-execution-of-describe-and-test-blocks). Once this runs, it will stay that way for the rest of the test run since console is a global object. (Node v16.0.0. Jest 24.9.0). – jgreen Jun 15 '21 at 21:23
  • For me this creates a mock that I can check has been called the appropriate number of times but doesn't suppress the resulting message. To do that I had to overload the global object in my setup. – Peter Gerdes Jun 22 '21 at 13:53
  • 5
    I don't know why this answer has so many upvotes. It's a great way to disable console functionality before each test (as the name `beforeEach` would imply), but it doesn't answer the OP's question, which is "how to disable console errors inside a _specific_ Jest test". – fenix.shadow Sep 15 '21 at 18:25
  • 7
    @fenix.shadow it's very easily adaptable to doing it inside a single test. Anything that can be done within a `beforeEach` can be done within an `it`. As for people saying it doesn't work... it does for me. You may want to also catch the error thrown by Vue Test Utils's default error handler. – Ariane Oct 07 '21 at 15:46
  • 4
    The missing part of adapting it to be used in a single test is calling `consoleSpy.mockRestore()` afterward. The code given in this solution will leave console logging disabled for all tests in the JS file that are executed afterward which can hide errors. The OP's original code sample fixes this. – Erik Hermansen Jan 08 '22 at 23:07
  • 1
    Spies are isolated per test – Constantin Jan 10 '22 at 16:16
85

As every test file runs in its own thread there is no need to restore it if you want to disable it for all test in one file. For the same reason you can also just write

console.log = jest.fn()
expect(console.log).toHaveBeenCalled();
Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297
  • 2
    Thank you for the info on that matter. It does make sense :) I was looking for a way to make it that way only inside a specific test without having to restore it (I initially thought that was the behaviour by default), but I guess beforeEach does the trick. – Apidcloud Jun 12 '17 at 11:01
  • 1
    But the next tests in the same file will still have it mocked, right? Depending on the situation, that may not be ideal. – Ariane Oct 07 '21 at 15:47
30

I found that the answer above re: suppressing console.log across all test suites threw errors when any other console methods (e.g. warn, error) were called since it was replacing the entire global console object.

This somewhat similar approach worked for me with Jest 22+:

package.json

"jest": {
  "setupFiles": [...],
  "setupTestFrameworkScriptFile": "<rootDir>/jest/setup.js",
  ...
}

jest/setup.js

jest.spyOn(global.console, 'log').mockImplementation(() => jest.fn());

Using this method, only console.log is mocked and other console methods are unaffected.

nickb
  • 2,870
  • 2
  • 25
  • 14
13

To me a more clear/clean way (reader needs little knowledge of the jest API to understand what is happening), is to just manually do what mockRestore does:

// at start of test you want to suppress
const consoleLog = console.log;
console.log = jest.fn();

// at end of test
console.log = consoleLog;
Michael Liquori
  • 619
  • 10
  • 16
  • 1
    You also need to cover console.info, console.error, console.warn, etc. – Michael Oryl Oct 24 '19 at 15:00
  • 1
    @michael-liquori why do you need to restart the console.log? I think after every describe the mocks are cleared – Jhonatan Jul 09 '20 at 14:03
  • 2
    @Jhonatan I don't think it does clear after every describe, though I haven't tested this recently to be sure. According to [jest docs](https://jestjs.io/docs/en/configuration.html#resetmocks-boolean) there is a `clearMocks` and `resetMocks` configuration option but they both default to `false`, **and** neither of those actually restore the initial implementation even if set to `true`. And, considering this is a config option that could be changed at some point, I think it is best practice to clean up manually to ensure your tests won't cause problems in the future. – Michael Liquori Jul 09 '20 at 20:23
  • Note that if the test fails, the rest of the test will not run so `console.log = consoleLog` will not be called. Better to `beforeEach(() => jest.restoreAllMocks())`. – Tamlyn Jan 27 '23 at 14:53
7
beforeAll(() => {
    jest.spyOn(console, 'log').mockImplementation(() => {});
    jest.spyOn(console, 'error').mockImplementation(() => {});
    jest.spyOn(console, 'warn').mockImplementation(() => {});
    jest.spyOn(console, 'info').mockImplementation(() => {});
    jest.spyOn(console, 'debug').mockImplementation(() => {});
});
Dmitry Grinko
  • 13,806
  • 14
  • 62
  • 86
5

Here's all the lines you may want to use. You can put them right in the test:

jest.spyOn(console, 'warn').mockImplementation(() => {});
console.warn("You won't see me!")
expect(console.warn).toHaveBeenCalled();
console.warn.mockRestore();
Dr-Bracket
  • 4,299
  • 3
  • 20
  • 28
3

Weirdly the answers above (except Raja's great answer but I wanted to share the weird way the others fail and how to clear the mock so no one else wastes the time I did) seem to successfully create the mock but don't suppress the logging to the console.

Both

const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});

and

global console = {
   warn: jest.fn().mockImplementation(() => {});
}

successfully install the mock (I can use expect(console.warn).toBeCalledTimes(1) and it passes) but it still outputs the warning even though the mock implementation seemingly should be replacing the default (this is in a jsdom environment).

Eventually I found a hack to fix the problem and put the following in the file loaded with SetupFiles in your config (note that I found sometimes global.$ didn't work for me when putting jquery into global context so I just set all my globals this way in my setup).

const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
const consoleDebug = jest.spyOn(console, 'debug').mockImplementation(() => {});
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});


Object.defineProperty(global, 'console', {value: {
                                            warn: consoleWarn,
                                            log: consoleLog,
                                            debug: consoleDebug,
                                            error: consoleError}});

It feels ugly and I then have to put code like the following in each test file since beforeEach isn't defined in the files referenced by SetupFiles (maybe you could put both in SetupFilesAfterEnv but I haven't tried).

beforeEach(() => {
  console.warn.mockClear();
});
Peter Gerdes
  • 2,288
  • 1
  • 20
  • 28
3

If you are using command npm test to run test then change the test script in package.json like below

{
  ....
  "name": "....",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest --silent",         // add --silent to jest in script like this
    "lint": "eslint ."
  },
  ...
}

Or else you can directly run command npx jest --silent to get rid of all logs and errors when testing

Thanhal P A
  • 4,097
  • 3
  • 18
  • 38
2

Since jest.spyOn doesn't work for this (it may have in the past), I resorted to jest.fn with a manual mock restoration as pointed out in Jest docs. This way, you should not miss any logs which are not empirically ignored in a specific test.

const consoleError = console.error

beforeEach(() => {
  console.error = consoleError
})

test('with error', () => {
  console.error = jest.fn()
  console.error('error') // can't see me
})

test('with error and log', () => {
  console.error('error') // now you can
})
adi518
  • 863
  • 1
  • 11
  • 19
1

Kudos to @Raja's top answer. Here is what I am using (I would comment, but can't share a multi-line code block in a comment).

With jest v26, I'm getting this error:

We detected setupFilesAfterEnv in your package.json.

Remove it from Jest configuration, and put the initialization code in src/setupTests.js:
This file will be loaded automatically.

Therefore, I had to remove the setupFilesAfterEnv from my jest config, and add this to src/setupTests.js

// https://stackoverflow.com/questions/44467657/jest-better-way-to-disable-console-inside-unit-tests
const nativeConsoleError = global.console.error

global.console.error = (...args) => {
  if (args.join('').includes('Could not parse CSS stylesheet')) {
    return
  }
  return nativeConsoleError(...args)
}
Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
0

Another approach is to use process.env.NODE_ENV. This way one can selectively choose what to show (or not) while running tests:

if (process.env.NODE_ENV === 'development') {
  console.log('Show output only while in "development" mode');
} else if (process.env.NODE_ENV === 'test') {
  console.log('Show output only while in "test" mode');
}

or

const logDev = msg => {
  if (process.env.NODE_ENV === 'development') {
    console.log(msg);
  }
}
logDev('Show output only while in "development" mode');

This will require this configuration to be placed on package.json:

"jest": {
  "globals": {
    "NODE_ENV": "test"
  }
}

Note that this approach is not a direct solution to the original question, but gives the expected result as long as one has the possibility to wrap the console.log with the mentioned condition.

Wallace Sidhrée
  • 11,221
  • 6
  • 47
  • 58
  • 3
    What the author of the question is how to disable console.log on testing. This solution is not optimal. – Erick Oct 01 '19 at 16:12
  • 1
    For copy-pasters out there: replace `===` with `!==` according to your needs. I've been using this approach for years and it works flawlessly, but I do make adjustments according to my needs. – Wallace Sidhrée Oct 07 '19 at 08:09
  • Doesn't answer the actual question. – Michael Oryl Oct 24 '19 at 14:59
  • This is a hacky solution and not customizable. What if disable only for a specific test and not the other one? – Jhonatan Jul 09 '20 at 14:05