An assertion-free test file is indistinguishable from a passing test in CI — it runs, it exits 0, and it tells you nothing. AI-generated test suites routinely produce these: scaffolded describe/it blocks that call setup helpers but never verify a return value, a side effect, or a thrown error. The gap only surfaces when a real defect slips to production and you realize your test suite was never checking anything. ISO-25010:2011 testability requires that tests can actually detect faults; assertion-free files by definition cannot.
Critical because a test that cannot fail provides zero defect-detection value regardless of how many times it runs.
Add at least one assertion to every it/test block. Every call to createUser, fetchOrder, or any production function should be followed by an expect on its return value or side effect. Replace empty or setup-only test bodies with real verifications:
// Before: passes when createUser is completely broken
it('creates a user', async () => {
await createUser({ email: 'test@example.com' })
})
// After: fails if id, email, or timestamp is wrong
it('creates a user', async () => {
const user = await createUser({ email: 'test@example.com' })
expect(user.id).toBeDefined()
expect(user.email).toBe('test@example.com')
expect(user.createdAt).toBeInstanceOf(Date)
})
ID: ai-slop-test-theater.assertion-quality.every-test-file-has-assertion
Severity: critical
What to look for: Walk all test files. Count all test files found and verify each contains at least 1 of these assertion patterns: expect(, assert(, assert., .toBe(, .toEqual(, .toMatch(, .toHaveBeenCalled, .toHaveBeenCalledWith(, .toContain(, .toBeTruthy(, .toBeFalsy(, .toThrow(, .toResolve(, .toReject(, .should., should(, chai., t.is(, t.equal(, t.deepEqual(, t.true(, t.false(, t.throws(, t.notThrows( (AVA), expect.assertions(, .toMatchSnapshot(, .toMatchInlineSnapshot(. Count: total test files, total with at least 1 assertion, total assertion-free.
Pass criteria: 100% of test files contain at least 1 assertion pattern. Report: "X test files inspected, Y with assertions, 0 assertion-free."
Fail criteria: At least 1 test file has zero assertion patterns.
Do NOT pass when: A test file contains only it( calls with empty bodies or bodies that only call setup functions — those don't count as assertions.
Skip (N/A) when: Project has 0 test files.
Report even on pass: Always report the count of test files inspected and the assertion library detected. Example: "Test framework: vitest. 47 test files inspected, 47 with assertions (100%)."
Cross-reference: For broader code quality metrics including test presence and structure, the Code Quality Essentials audit (code-quality-essentials) covers test organization patterns.
Detail on fail: "3 assertion-free test files: tests/auth.test.ts (only contains describe/it blocks with empty bodies), src/lib/__tests__/utils.test.ts (calls helper functions but no expect calls)"
Remediation: A test file with zero assertions tests nothing — it just exercises the code path and counts as a "passing test" without verifying anything. Add real assertions:
// Bad: no assertion, this passes even when the function is broken
it('creates a user', async () => {
const user = await createUser({ email: 'test@example.com' })
})
// Good: actually verifies the result
it('creates a user', async () => {
const user = await createUser({ email: 'test@example.com' })
expect(user.id).toBeDefined()
expect(user.email).toBe('test@example.com')
expect(user.createdAt).toBeInstanceOf(Date)
})