Agent Skills: Improving Weak Tests

Rewrite a weak unit spec into one that kills mutations. Use when you must fix a toBeTruthy/toBeDefined/toBeFalsy assertion, a bare mock-fired check, an array-length-only check, a shapeless snapshot, or a private-state peek. Triggers on "rewrite this spec", "stronger assertions", "catch mutations".

UncategorizedID: ANcpLua/ancplua-claude-plugins/improving-weak-tests

Install this agent skill to your local

pnpm dlx add-skill https://github.com/ANcpLua/ancplua-claude-plugins/tree/HEAD/plugins/mutation-minded-testing/skills/improving-weak-tests

Skill Files

Browse the full folder contents for improving-weak-tests.

Download Skill

Loading file tree…

plugins/mutation-minded-testing/skills/improving-weak-tests/SKILL.md

Skill Metadata

Name
improving-weak-tests
Description
Rewrite a weak unit spec into one that kills mutations. Use when you must fix a toBeTruthy/toBeDefined/toBeFalsy assertion, a bare mock-fired check, an array-length-only check, a shapeless snapshot, or a private-state peek. Triggers on "rewrite this spec", "stronger assertions", "catch mutations".

Improving Weak Tests

Core principle

Every rewrite must answer: what plausible mutation does the new test catch that the old one did not? If you cannot name that mutation in one sentence, the rewrite is not done.

The catalogue below is written in TypeScript against jest/vitest matchers (toEqual, toHaveBeenCalledWith, expect.any), but the weak-assertion patterns are language-agnostic — the same rewrites apply to pytest in Python (see the pytest note under rule 1).

The catalogue

1. toBeTruthy() → exact shape

// WEAK
expect(await saveUser(input)).toBeTruthy();

// STRONG
const result = await saveUser(validInput);
expect(result).toEqual({
  id: expect.any(String),
  email: validInput.email,
  createdAt: expect.any(Date),
});

Catches: saveUser returning {} or echoing the input.

The same weakness in pytest is a bare truthy assert; rewrite it to pin the exact shape:

# WEAK
assert save_user(valid_input)

# STRONG
from datetime import datetime

result = save_user(valid_input)
assert result.email == valid_input.email
assert isinstance(result.id, str)
assert isinstance(result.created_at, datetime)

2. "mock was called" → state + args + output

// WEAK
await vm.saveUser(input);
expect(httpClient.post).toHaveBeenCalled();

// STRONG
await vm.saveUser(input);
expect(httpClient.post).toHaveBeenCalledWith(`${baseUrl}/users`, input);
expect(vm.users()).toContainEqual(expect.objectContaining({ email: input.email }));
expect(vm.isFormVisible()).toBe(false);
expect(vm.errorMessage()).toBeNull();

Catches: the handler not reloading, not closing the form, or silently failing.

3. Happy path only → add failure path + negative space

it('keeps form open and exposes error when create fails', async () => {
  vm.openCreateForm();
  const promise = vm.saveUser(validInput);
  httpTesting.expectOne('POST', `${baseUrl}/users`)
    .error(new ProgressEvent('error'));
  await promise;
  expect(vm.isFormVisible()).toBe(true);
  expect(vm.errorMessage()).toMatch(/create failed|error/i);
  expect(vm.isSaving()).toBe(false);
  expect(vm.users()).toEqual([]);
});

it('does not issue a write when input is invalid', async () => {
  const promise = vm.saveUser(invalidInput);
  await promise;
  httpTesting.expectNone('POST', `${baseUrl}/users`);
  expect(vm.errorMessage()).toMatch(/invalid/i);
});

Catches: silent error swallowing and missing input validation.

4. array.length === N → content + order

// WEAK
expect(vm.users().length).toBe(1);

// STRONG
expect(vm.users()).toEqual([
  expect.objectContaining({ id: createdUser.id, email: createdUser.email }),
]);

Catches: returning [anything] instead of the real user.

5. Implementation peek → public contract

If a test reads _internalMap or calls resetForTesting(), rewrite against a public observable outcome. If no public outcome exists, the behavior has no observable contract — hand to reviewing-testability rather than keep the peek.

6. Empty snapshot → hand-crafted structural assertion

// WEAK
expect(vm.state()).toMatchSnapshot();

// STRONG
expect(vm.state()).toEqual({
  phase: 'loaded',
  users: expect.arrayContaining([
    expect.objectContaining({ id: createdUser.id }),
  ]),
  selectedUserId: createdUser.id,
  error: null,
});

7. Timeout waits → condition-based waiting

// WEAK
await new Promise(r => setTimeout(r, 100));
expect(vm.loaded()).toBe(true);

// STRONG
await waitFor(() => vm.loaded() === true);
expect(vm.users()).toEqual(expectedUsers);

8. State transition: before → during → after

expect(vm.isSaving()).toBe(false);
const p = vm.saveUser(validInput);
expect(vm.isSaving()).toBe(true);
// flush HTTP
await p;
expect(vm.isSaving()).toBe(false);
expect(vm.users()).toContainEqual(
  expect.objectContaining({ email: validInput.email }),
);

Catches: the saving flag never being set, or never being cleared.

Hard rules

  • Never use toBeTruthy(), toBeDefined(), toBeFalsy() as a primary assertion.
  • Never rely on toHaveBeenCalled() alone — pair with toHaveBeenCalledWith(specific) and a state or output assertion, or drop the mock check.
  • Never assert .length without content.
  • Never leave a behavior with only a positive-path test.
  • Never write expect(true).toBe(true) or equivalent padding.
  • If you cannot assert something specific because the code shape prevents it, stop. Hand the case to reviewing-testability rather than writing a compromise test.

Output

For each rewrite, one diff block:

### <file>:<test name>

**Before (WEAK)**
<old body>

**After (STRONG)**
<new body>

**Mutation caught:** one sentence.

Ship the negative-space counterpart in the same unit of work when the original covered only the happy path.

Related

  • Before rewriting: run judging-test-quality to confirm the verdict.
  • If rewriting keeps hitting architectural walls: reviewing-testability.
  • When driving a whole file or module to full coverage: mutation-resistant-coverage.