# No catch-all try/catch blocks that swallow errors

- **Pattern:** `ab-000293` (`ai-slop-test-theater.assertion-quality.no-error-swallowing-try-catch`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000293
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000293)

## Why it matters

A `try/catch` block in a test that has no assertion, no `throw`, and no `console.error` converts every runtime error into a silent pass. When the production function throws — due to a network failure, a missing DB record, an uncaught null — the test catches it, discards it, and reports success. This pattern is common in AI-generated tests that wrap async calls defensively without understanding that swallowing errors in tests defeats the entire purpose of having tests. ISO-25010:2011 fault-tolerance requires that faults be detectable, not hidden.

## Severity rationale

High because silent error-swallowing means production failures that would be caught by tests instead generate a green CI run.

## Remediation

Replace bare `catch {}` or `catch { /* ignore */ }` blocks in tests with either an `expect(...).rejects.toThrow()` assertion or a re-throw. Never discard an error inside a test:

```ts
// Before: fetchUser throwing is treated as success
it('fetches user', async () => {
  try {
    await fetchUser('123')
  } catch {
    // ignore
  }
})

// After: error path is explicitly asserted
it('throws for unknown user', async () => {
  await expect(fetchUser('does-not-exist')).rejects.toThrow('User not found')
})

// Or: surface real failures immediately
it('fetches user', async () => {
  const user = await fetchUser('123')
  expect(user.id).toBe('123')
})
```

## Detection

- **ID:** `no-error-swallowing-try-catch`
- **Severity:** `high`
- **What to look for:** Walk all test files. Count all `try {` blocks within an `it(`/`test(`/`describe(` callback where the corresponding `catch` block contains: NO assertion call (`expect(`/`assert(`), AND NO `throw`, AND NO `console.error`. Such catch blocks silently swallow errors and let the test "pass" even when the system under test throws.
- **Pass criteria:** 0 error-swallowing catch blocks in test files. Report: "X test files inspected, 0 error-swallowing catches found."
- **Fail criteria:** At least 1 try/catch in a test where the catch block has no assertion, no throw, and no error log.
- **Do NOT pass when:** A catch block ends with `// ignore` or `/* swallow */` — those are still swallowing errors.
- **Skip (N/A) when:** Project has 0 test files.
- **Detail on fail:** `"1 error-swallowing catch: tests/api.test.ts line 22 wraps fetchUser() in try { ... } catch { /* ignore */ } — if fetchUser throws, the test passes silently"`
- **Remediation:** A catch block with no assertion converts every test failure into a test pass. Either assert the error OR re-throw it:

  ```ts
  // Bad: silently swallows errors
  it('fetches user', async () => {
    try {
      await fetchUser('123')
    } catch {
      // ignore — but the test passes when fetchUser is broken
    }
  })

  // Good: assert the expected error
  it('throws on invalid user', async () => {
    await expect(fetchUser('invalid')).rejects.toThrow('User not found')
  })

  // Or: re-throw to surface real failures
  it('fetches user', async () => {
    try {
      const user = await fetchUser('123')
      expect(user.id).toBe('123')
    } catch (err) {
      throw err  // surface the actual failure
    }
  })
  ```

## External references

- iso-25010 reliability.fault-tolerance

Taxons: code-quality

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