ValentinH / jest-fail-on-console

Utility to make jest tests fail when console.error() or any other methods are used
MIT License
141 stars 20 forks source link

[2.2.1] Calling failOnConsole() via setupFilesAfterEnv now fails with "Test did not tear down console.error mock properly" #13

Closed caugner closed 2 years ago

caugner commented 2 years ago

We call failOnConsole() in a jest.setup.js script that we have configured in jest.config.js using the setupFilesAfterEnv config option.

// jest.setup.js

import failOnConsole from 'jest-fail-on-console'

failOnConsole({
  silenceMessage(message) {
    if (
      message.startsWith('[BootstrapVue warn]') ||
      message.startsWith('[Vue warn]: Injection "Symbol(pinia)" not found')
    ) {
      return true
    }
    return false
  },
})

Dependabot opened a MR today to bump jest-fail-on-console from 2.1.1 to 2.2.1, but unfortunately this causes all tests to fail with the following error message:

    Test did not tear down console.error mock properly.
        If you are trying to disable the "fail on console" mechanism, you should use beforeEach/afterEach
        instead of beforeAll/afterAll

      at flushUnexpectedConsoleCalls (node_modules/jest-fail-on-console/index.js:50:13)
      at Object.flushAllUnexpectedConsoleCalls (node_modules/jest-fail-on-console/index.js:97:7)

The changelog doesn't explain this change in behaviour, and it is working as expected with 2.1.1, despite not using any {before,after}* methods, so I'm wondering if this is intended.

ValentinH commented 2 years ago

Thank you for the report and sorry for the inconvenience.

Are you able to reproduce this issue with a simple example please?

ValentinH commented 2 years ago

Moreover, could you try with the 2.2.0 version?

ValentinH commented 2 years ago

Lastly, could you try to change this line in your node_modules: https://github.com/ricardo-ch/jest-fail-on-console/blob/main/index.js#L42

By:

return newMethod
caugner commented 2 years ago

2.2.0 is working fine, and changing that line to return newMethod resolves the isue with 2.2.1 as well.

ValentinH commented 2 years ago

Perfect, I'll publish a fix ASAP then.

caugner commented 2 years ago

Awesome, thanks a lot for the quick resolution! 🚀

ValentinH commented 2 years ago

Hmm, actually, if I try this on one of my project, I start getting the same error as you. 🤔

What version of Jest are you using?

caugner commented 2 years ago

What version of Jest are you using?

24.9.0 (via @vue/cli-plugin-unit-jest)

ValentinH commented 2 years ago

Ok this is probably why. I'm using v27.

I did a quite heavy refactor that also simplify the logic, could you try it please?

const util = require('util')
const chalk = require('chalk')

const defaultErrorMessage = (methodName, bold) =>
  `Expected test not to call ${bold(`console.${methodName}()`)}.\n\n` +
  `If the ${methodName} is expected, test for it explicitly by mocking it out using ${bold(
    'jest.spyOn'
  )}(console, '${methodName}').mockImplementation() and test that the warning occurs.`

const init = ({
  silenceMessage,
  shouldFailOnWarn = true,
  shouldFailOnError = true,
  shouldFailOnLog = false,
  errorMessage = defaultErrorMessage,
} = {}) => {
  const flushUnexpectedConsoleCalls = (methodName, unexpectedConsoleCallStacks) => {
    if (unexpectedConsoleCallStacks.length > 0) {
      const messages = unexpectedConsoleCallStacks.map(([stack, message]) => {
        const stackLines = stack.split('\n')
        return (
          `${chalk.red(message)}\n` +
          `${stackLines
            .map((line, index) => {
              if (index === stackLines.length - 1) {
                return chalk.white(line)
              }
              return chalk.gray(line)
            })
            .join('\n')}`
        )
      })

      const message = errorMessage(methodName, chalk.bold)

      throw new Error(`${message}\n\n${messages.join('\n\n')}`)
    }
  }

  const patchConsoleMethod = (methodName) => {
    const unexpectedConsoleCallStacks = []

    const newMethod = (format, ...args) => {
      const message = util.format(format, ...args)
      if (silenceMessage && silenceMessage(message, methodName)) {
        return
      }

      // Capture the call stack now so we can warn about it later.
      // The call stack has helpful information for the test author.
      // Don't throw yet though b'c it might be accidentally caught and suppressed.
      const { stack } = new Error()
      if (stack) {
        unexpectedConsoleCallStacks.push([stack.substr(stack.indexOf('\n') + 1), message])
      }
    }

    let originalMethod = console[methodName]

    beforeEach(() => {
      console[methodName] = newMethod // eslint-disable-line no-console
      unexpectedConsoleCallStacks.length = 0
    })

    afterEach(() => {
      flushUnexpectedConsoleCalls(methodName, unexpectedConsoleCallStacks)
      console[methodName] = originalMethod
    })
  }

  if (shouldFailOnError) {
    patchConsoleMethod('error')
  }
  if (shouldFailOnWarn) {
    patchConsoleMethod('warn')
  }
  if (shouldFailOnLog) {
    patchConsoleMethod('log')
  }
}

module.exports = init
ValentinH commented 2 years ago

The code in the above PR is published as 2.2.2-alpha.0 if you want to try it.

ValentinH commented 2 years ago

A fix was published in v2.2.2

caugner commented 2 years ago

Sorry for the late response, and thank you! v2.2.2 is working like a charme. 🎉