VOOZH about

URL: https://dev.to/recca0120/vitest-fail-on-console-stop-ignoring-consoleerror-in-your-tests-1io9

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


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

Setup

Import and call it in your setup file:

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

failOnConsole()

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

import { defineConfig } from 'vitest/config'

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

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

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

silenceMessage

Like allowMessage, but also suppresses the console output entirely:

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

skipTest

Skip specific test files or test names entirely:

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

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

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

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