Jest: Better way to disable console inside unit tests

JavascriptUnit TestingMockingJestjs

Javascript Problem Overview


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.

Thanks!

Javascript Solutions


Solution 1 - Javascript

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"],
};

Solution 2 - Javascript

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

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

Solution 3 - Javascript

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();

Solution 4 - Javascript

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.

Solution 5 - Javascript

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;

Solution 6 - Javascript

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(() => {});
});

Solution 7 - Javascript

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();
});

Solution 8 - Javascript

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();

Solution 9 - Javascript

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

Solution 10 - Javascript

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

Solution 11 - Javascript

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.

Solution 12 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionApidcloudView Question on Stackoverflow
Solution 1 - JavascriptRaja JaganathanView Answer on Stackoverflow
Solution 2 - JavascriptConstantinView Answer on Stackoverflow
Solution 3 - JavascriptAndreas KöberleView Answer on Stackoverflow
Solution 4 - JavascriptnickbView Answer on Stackoverflow
Solution 5 - JavascriptMichael LiquoriView Answer on Stackoverflow
Solution 6 - JavascriptDmitry GrinkoView Answer on Stackoverflow
Solution 7 - JavascriptPeter GerdesView Answer on Stackoverflow
Solution 8 - JavascriptDr-BracketView Answer on Stackoverflow
Solution 9 - JavascriptDevin RhodeView Answer on Stackoverflow
Solution 10 - JavascriptThanhalView Answer on Stackoverflow
Solution 11 - JavascriptWallace SidhréeView Answer on Stackoverflow
Solution 12 - Javascriptadi518View Answer on Stackoverflow