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.

Why is this an issue?

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:

This 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.

Code examples

Noncompliant code example

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

Compliant solution

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

Noncompliant code example

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

Compliant solution

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

Noncompliant code example

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

Compliant solution

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

Noncompliant code example

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

Compliant solution

describe("user service", () => {
  beforeEach(() => {
    seedUsers();
  });

  describe("admin operations", () => {
    beforeEach(() => {
      grantAdmin("alice");
    });

    it("lists admins", () => {
      expect(listAdmins()).toEqual(["alice"]);
    });
  });
});

Resources

Documentation