# Setup and teardown are balanced

- **Pattern:** `ab-000297` (`ai-slop-test-theater.mock-hygiene.setup-teardown-balanced`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000297
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000297)

## Why it matters

When a `beforeAll` or `beforeEach` hook creates database rows, writes files, or inserts records without a matching `afterAll`/`afterEach` to remove them, test data accumulates across runs. Subsequent test runs operate on a progressively more polluted database: unique-constraint violations start appearing, count assertions become wrong, and test order begins to matter. This makes the test suite flaky and eventually unmaintainable. ISO-25010:2011 recoverability requires that test runs return the system to a clean state.

## Severity rationale

Medium because unbalanced setup causes intermittent failures and cross-test contamination that are difficult to diagnose, though immediate test runs may still pass.

## Remediation

Every `beforeAll`/`beforeEach` that creates state needs a corresponding `afterAll`/`afterEach` that removes it. Track created entity IDs in the outer scope and delete them in teardown:

```ts
describe('UserAPI', () => {
  let userId: string

  beforeAll(async () => {
    const user = await prisma.user.create({ data: { email: 'test@example.com' } })
    userId = user.id
  })

  afterAll(async () => {
    await prisma.user.deleteMany({ where: { id: userId } })
  })

  it('retrieves the user', async () => {
    const found = await prisma.user.findUnique({ where: { id: userId } })
    expect(found?.email).toBe('test@example.com')
  })
})
```

For simpler isolation, run each test inside a database transaction and roll it back in `afterEach` — no manual tracking required.

## Detection

- **ID:** `setup-teardown-balanced`
- **Severity:** `medium`
- **What to look for:** Walk all test files. For each file, count all `beforeAll(`/`beforeEach(` calls AND all `afterAll(`/`afterEach(` calls. For each test file with setup hooks containing state-creating operations (`.create(`, `prisma.X.create(`, `db.X.insert(`, `fs.writeFile(`, `fs.mkdir(`), verify there is a corresponding teardown hook in the same file (or imported from a setup helper).
- **Pass criteria:** 100% of files with state-creating setup hooks have corresponding teardown hooks. Report: "X files with setup hooks, Y with teardown, 0 unbalanced."
- **Fail criteria:** At least 1 test file has a `beforeAll(`/`beforeEach(` that creates state but no `afterAll(`/`afterEach(` to clean it up.
- **Skip (N/A) when:** No test files contain setup hooks with state creation.
- **Detail on fail:** `"1 unbalanced setup: tests/api.test.ts has beforeAll() that calls prisma.user.create() but no afterAll() to delete created users — tests pollute the database between runs"`
- **Remediation:** Without teardown, tests pollute the database and affect subsequent runs. Always pair setup with teardown:

  ```ts
  describe('UserAPI', () => {
    let userId: string

    beforeAll(async () => {
      const user = await prisma.user.create({ data: { email: 'test@example.com' } })
      userId = user.id
    })

    afterAll(async () => {
      await prisma.user.delete({ where: { id: userId } })
    })

    it('finds the user', async () => {
      const found = await prisma.user.findUnique({ where: { id: userId } })
      expect(found).toBeTruthy()
    })
  })
  ```

  Better: use a transaction-rollback pattern so setup is implicitly cleaned up.

## External references

- iso-25010 reliability.recoverability

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-000297
