This rule raises an issue when lifecycle hooks (beforeAll, beforeEach, afterEach, afterAll,
plus Mocha’s before/after aliases) are interleaved with test cases (it, test) or nested suites
(describe()) inside the same test suite. before* hooks must appear before any test case or nested suite; after*
hooks must appear either all at the top, alongside before* hooks, or all at the bottom, after every test case and nested suite.
Test suites are easier to understand when lifecycle hooks sit in predictable positions. Hooks control the setup and teardown that runs before or
after tests, so grouping them outside the test cases makes the execution flow immediately clear. before* hooks belong at the top because
they run first; after* hooks may sit at the top alongside them, or at the bottom of the suite — mirroring the try/finally mental model of
setup, then tests, then teardown.
When hooks are scattered between test cases, readers must scan through the entire suite to understand what preparation or cleanup happens around
each test. This creates unnecessary cognitive load and can lead to misunderstandings about test behavior. For example, a developer reading a test case
might assume it runs in isolation, not realizing that a beforeEach hook declared later in the file seeds data or modifies global state.
This hidden dependency makes debugging harder and increases the risk of incorrect assumptions during maintenance.
Lifecycle hooks in testing frameworks like Jest, Mocha, and Vitest execute in a predictable order, regardless of where they appear in the source:
beforeAll (or Mocha’s before) runs once before all tests in the suitebeforeEach runs before each individual testafterEach runs after each individual testafterAll (or Mocha’s after) runs once after all tests completeThis rule applies per describe scope: nested describe blocks are analyzed independently, with their own hooks subject to
the same placement rules.
Move every lifecycle hook so that before* hooks appear at the top of the suite and after* hooks appear either all at the
top, alongside before* hooks, or all at the bottom, after every test case and nested suite.
describe("user service", () => {
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
beforeEach(() => { // Noncompliant: setup hook declared after a test case
seedUsers();
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
afterEach(() => {
clearDatabase();
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
afterEach(() => {
clearDatabase();
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
afterEach(() => { // Noncompliant: teardown hook interleaved between test cases
clearDatabase();
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
afterEach(() => {
clearDatabase();
});
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
afterEach(() => {
clearDatabase();
});
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
afterAll(() => { // Noncompliant: teardown hooks must be grouped together
closeConnection();
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
it("returns list of users", () => {
expect(listUsers()).toEqual(["alice", "bob"]);
});
it("filters users by role", () => {
expect(filterByRole("admin")).toEqual(["alice"]);
});
afterEach(() => {
clearDatabase();
});
afterAll(() => {
closeConnection();
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
describe("admin operations", () => {
it("lists admins", () => {
expect(listAdmins()).toEqual(["alice"]);
});
beforeEach(() => { // Noncompliant: setup hook declared after a test case in the nested suite
grantAdmin("alice");
});
});
});
describe("user service", () => {
beforeEach(() => {
seedUsers();
});
describe("admin operations", () => {
beforeEach(() => {
grantAdmin("alice");
});
it("lists admins", () => {
expect(listAdmins()).toEqual(["alice"]);
});
});
});