no-conditional-tests

July 2, 2026 Β· View on GitHub

πŸ“ Disallow conditionally registering tests, suites, and hooks.

πŸ’ΌπŸš« This rule is enabled in the βœ… recommended config. This rule is disabled in the β˜‘οΈ unopinionated config.

Registering a test, suite, or hook inside a condition makes the suite structure depend on runtime state, so the tests, setup, and teardown that run vary between environments. A test silently skipped by a condition looks the same as a passing run, and a skipped hook can make setup or teardown silently differ. Put the condition inside the test or hook body, or split the file by environment instead.

This rule reports a test/it/describe/suite/before/after/beforeEach/afterEach call guarded by an if, else, ternary, logical (&&/||/??), or switch. Loops are allowed, since iterating to register tests is the idiomatic way to write table-driven tests in node:test. The rule only checks lexical ancestors of the registration call and does not trace callbacks passed to helper methods.

Examples

import {test, describe, beforeEach} from 'node:test';

// ❌
if (process.env.CI) {
	test('runs only on CI', () => {});
}

process.platform === 'darwin' && test('macOS only', () => {});

if (process.env.CI) {
	beforeEach(() => {
		startServer();
	});
}

// βœ… (condition inside the test)
test('skips off CI', t => {
	if (!process.env.CI) {
		t.skip('CI only');
		return;
	}

	assert.ok(check());
});

// βœ… (table-driven via a loop)
for (const testCase of cases) {
	test(testCase.name, () => {
		assert.strictEqual(run(testCase.input), testCase.output);
	});
}