← Back to Blog
Testing A brick building facade whose windows have all been smashed one after another, illustrating the Broken Windows Effect.

A test that pollutes the console should fail: the Broken Windows Effect

Why a green test that pollutes stdout/stderr should be red, and how the Broken Windows Effect applies to the quality of your tests and your codebase.

📅 ✍️ Antoine Coulon
testingvitestjestbroken-windowscode-quality

A test can be green and still lie. It passes, the CI shows its tidy little ✅, and yet on every run it dumps a cascade of warnings and errors into the console. We’ve grown used to it: that noise is part of the scenery, we scroll past it without seeing it anymore. That’s exactly where the problem starts.

My stance is simple: a test that succeeds (🟢) but pollutes the console (stdout or stderr) should be treated as a test that fails (🔴). Not out of purism, but because that pollution is almost always the visible symptom of a very real problem.

Why a warning shouldn’t go unanswered

When a test spits noise into the console, it’s tempting to shrug it off: “it passes, so everything’s fine.” But that noise has causes, and it has costs.

None of these reasons is dramatic on its own. It’s precisely their silent accumulation that’s the problem, and that’s where a theory from another field comes in.

The trap of the Broken Windows Effect

The Broken Windows Effect comes from a theory formulated by George L. Kelling, a criminology professor, and James Q. Wilson, a political science professor. Their observation: if a window in a building is broken and left unrepaired, all the other windows will soon end up broken too.

The mechanism is psychological. A degradation left in place sends a signal: nobody is watching here, nobody cares. From then on, the next degradation costs less morally, and the decline accelerates on its own.

The analogy with tests is direct. The first tolerated warning is the first broken window. As long as it stands alone, you might think it’s harmless. But it sets a new norm: since we accept that noise, why not the next? One thing leads to another, the console becomes unreadable, no one can tell signal from noise anymore, and you lose the ability to react when a real alert shows up.

The principle doesn’t stop at the test suite. It applies far more broadly, across an entire codebase: a // TODO never dealt with, a tolerated any, a lint warning disabled “temporarily”… every broken window you leave in place makes the next one easier to accept. Making sure these warnings never start to settle in is maintaining the building before it falls into disrepair.

Two ways to respond to the noise

Faced with a test that pollutes the console, you have two approaches, and they are not equal.

Approach 1: silence the symptom

The first consists of hiding the logs. You can monkey patch console.log and console.error before a test to neutralize them, or rely on the runners’ native options:

jest --silent
vitest --silent

It’s fast, and the verbosity disappears. But here you’re only treating the symptom. The warning still exists; you’ve simply decided not to see it anymore. And by hiding it, you deprive yourself of useful information, tied to something that may eventually cause real problems in production. It’s putting tape over the warning light on the dashboard.

Approach 2: hunt for the root cause

The second approach is the one I favor, in a Lean spirit: trace things back to the root causes and understand the why of the warning. Most of the time, it’s there for a good reason. Rather than ignoring it, you simply set out to fix the underlying problem: the misused API, the badly injected dependency, the resource left open. Once the cause is handled, the noise disappears on its own, and for good.

To make this discipline impossible to bypass, you can go further and force tests to fail as soon as anything is written to stdout or stderr. The vitest-fail-on-console package integrates with Vitest to do exactly that:

import failOnConsole from "vitest-fail-on-console";

failOnConsole({
  shouldFailOnerror: true,
  shouldFailOnWarn: true,
  shouldFailOnLog: false,
  shouldFailOnInfo: false,
  shouldFailOnDebug: false,
});

From then on, a test that emits a warning or an error in the console turns the suite red: the broken window triggers the alarm immediately, instead of being tolerated in silence.

This principle is neither isolated nor extreme: it’s applied all over the devtools ecosystem. Rush.js, for instance, fails a project’s build by default as soon as a warning is emitted. The logic is always the same: don’t let the noise accumulate until it becomes the norm.

Conclusion

A green test that pollutes the console is not a clean test: it’s a test that succeeds despite a problem it’s pointing right at you. The Broken Windows Effect reminds us that no degradation is ever truly isolated: the first broken window you tolerate opens the way for all the ones that follow.

So the right response isn’t to silence the noise, but to eliminate its cause. And to hold that discipline over time, the safest bet is to automate it: failing the suite as soon as a warning appears turns a good intention into a guardrail. A silent console isn’t a cosmetic detail: it’s proof, on every run, that nothing is quietly degrading behind the scenes.