This rule raises an issue when assertion statements appear outside any test case (it(), test()) or lifecycle hook
(beforeEach()/afterEach(), before()/after(), or beforeAll()/afterAll())
— for example, at the module’s top level or directly inside a describe() body.
Test frameworks like Jest, Vitest, Mocha, Jasmine, Cypress, and Playwright only attribute assertions to a test when they execute inside a test case
or one of its lifecycle hooks. An assertion placed at the module’s top level runs while the file is being loaded; one placed directly inside a
describe() body runs while the framework is collecting the suite. In both cases the assertion executes outside any individual test.
This applies to any assertion the rule recognizes: a framework’s built-in expect() as well as common assertion and mocking libraries
such as Chai, Sinon, Supertest, and Node.js’s built-in assert module.
This creates several problems:
beforeEach or beforeAll may
compare against fixtures, mocks, or other state that is not yet initialized, causing intermittent failures.A reliable test suite requires every assertion to run under the framework’s control so it is properly isolated, reported, and integrated with the testing workflow.
Move the assertion into a test case (it() or test()) so it is associated with a specific scenario. If the assertion
validates shared setup rather than a single scenario, move it into the relevant lifecycle hook (for example beforeEach() or
before()) instead.
In the following examples, the file is a test file executed by a test runner, but the assertion is placed outside any test case.
import { config } from './config';
expect(config.enabled).toBe(true); // Noncompliant: assertion at the top level, outside any test case
import { config } from './config';
it('is enabled by configuration', () => {
expect(config.enabled).toBe(true);
});
import { config } from './config';
describe('user service', () => {
expect(config.enabled).toBe(true); // Noncompliant: assertion inside the describe() body, outside any test case
});
import { config } from './config';
describe('user service', () => {
it('is enabled by configuration', () => {
expect(config.enabled).toBe(true);
});
});
This rule does not raise an issue when the assertion can legitimately run outside a test runner. For example, Node.js’s built-in
assert module is commonly used for runtime validation in production code, where there is no test case or lifecycle hook to move it
into.
import assert from 'node:assert';
export function connect(options) {
assert(options.url, 'a connection URL is required'); // Compliant: runtime validation, not a test
// ...
}
The same applies to assertion libraries used outside a test runner, for example in a standalone script.
const { expect } = require('chai');
function checkResult(value) {
expect(value).to.equal(1); // Compliant: not running under a test runner
}
checkResult(computeValue());
describe(name, fn)describedescribetest.describe()assert