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.

Why is this an issue?

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:

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

Exceptions

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());

Resources

Documentation