DEV Community

Recca Tsai
Recca Tsai

Posted on • Originally published at recca0120.github.io

vitest-fail-on-console: Stop Ignoring console.error in Your Tests

Originally published at recca0120.github.io

All tests pass, but the terminal is full of red console.error output. This is common and easy to ignore — the tests passed, after all. But those errors don't appear out of nowhere. Something went wrong; nobody just noticed.

vitest-fail-on-console does one thing: if console.error or console.warn appears during a test, that test fails. It forces you to acknowledge these messages instead of letting them drown in noise.

Why console.error in Tests Is a Code Smell

Vitest doesn't care about console output by default. You can console.error all day and tests still pass.

The problem is that console.error usually means something. It might be:

  • A React prop type warning
  • An async error that was caught but not properly handled
  • A third-party package telling you you're using it wrong
  • An error handler in your own code getting triggered

When these appear in tests, the test is running in a slightly broken state — it just didn't throw. Over time the test output becomes pure noise. Nobody reads it anymore, and the real signals get buried.

vitest-fail-on-console flips this: make console output a test failure, so you're forced to address it.

Installation

npm install -D vitest-fail-on-console
Enter fullscreen mode Exit fullscreen mode

Setup

Import and call it in your setup file:

// tests/setup.ts
import failOnConsole from 'vitest-fail-on-console'

failOnConsole()
Enter fullscreen mode Exit fullscreen mode

Then wire up the setup file in vitest.config.ts:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    setupFiles: ['tests/setup.ts'],
  },
})
Enter fullscreen mode Exit fullscreen mode

That's it. Any test that triggers console.error or console.warn will now fail.

Options

failOnConsole() accepts an options object to control which console methods trigger failures:

failOnConsole({
  shouldFailOnError: true,   // default true
  shouldFailOnWarn: true,    // default true
  shouldFailOnLog: false,    // default false
  shouldFailOnInfo: false,   // default false
  shouldFailOnDebug: false,  // default false
  shouldFailOnAssert: false, // default false
})
Enter fullscreen mode Exit fullscreen mode

error and warn are usually enough. Whether to include log / info / debug depends on your project's conventions.

allowMessage

Allow specific messages through without failing — useful for known third-party issues you can't fix right now:

failOnConsole({
  allowMessage: (message) => {
    return /ResizeObserver loop limit exceeded/.test(message)
  },
})
Enter fullscreen mode Exit fullscreen mode

silenceMessage

Like allowMessage, but also suppresses the console output entirely:

failOnConsole({
  silenceMessage: (message) => {
    return /Not implemented: navigation/.test(message)
  },
})
Enter fullscreen mode Exit fullscreen mode

skipTest

Skip specific test files or test names entirely:

failOnConsole({
  skipTest: ({ testPath, testName }) => {
    return testPath.includes('/legacy/')
  },
})
Enter fullscreen mode Exit fullscreen mode

afterEachDelay

Sometimes async operations call console methods after a test ends. This option adds a delay before checking:

failOnConsole({
  afterEachDelay: 100, // wait 100ms, default is 0
})
Enter fullscreen mode Exit fullscreen mode

Handling Expected console.error Calls

After installing vitest-fail-on-console, if a test is specifically verifying that console.error gets called, letting it fire naturally will cause the test to fail.

The correct approach is to mock it with vi.spyOn:

it('logs an error when request fails', () => {
  // mock it so the message doesn't actually reach the console
  vi.spyOn(console, 'error').mockImplementation(() => {})

  triggerSomethingThatLogsError()

  // assert it was called with the expected message
  expect(console.error).toHaveBeenCalledWith('Request failed')
})
Enter fullscreen mode Exit fullscreen mode

This does two things: the test explicitly declares "I know an error will be logged here," and it asserts the exact message. Much stricter than silently letting console.error through.

Pair It with a Clean Test Environment

vitest-fail-on-console handles the console output side. If your tests also have I/O boundaries to replace — filesystem, file watchers — you can pair it with memfs using the same philosophy: every aspect of the test environment should be under your control.

See }}">Testing a Filesystem Service with memfs + FakeWatchService for that approach.

References

Top comments (0)