joabgonzalez/ai-agents-skills

unit-testing

Unit testing patterns for frontend and backend. Trigger: When writing or reviewing unit tests for any layer.

First seen Jul 16, 2026

Installation

$ npx skills add joabgonzalez/ai-agents-skills --skill unit-testing

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from joabgonzalez/ai-agents-skills · top by installs.

npx skills add joabgonzalez/ai-agents-skills

Browse all from joabgonzalez/ai-agents-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 8
License Apache 2.0
Default branch main
Open issues 0
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.1
LicenseApache 2.0
More metadata
version
1.1
type
domain

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,979 B
  • docs SUMMARY.md 128 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 3 installs

SKILL.md

Unit Testing

Patterns for isolated, maintainable unit tests. Orchestrates jest and react-testing-library -- delegate to them for runner APIs and component queries.

When to Use

  • Writing unit tests for frontend or backend code
  • Reviewing test coverage, isolation, and structure
  • Refactoring an existing test suite

Don't use for:

  • Jest syntax and mock APIs -- delegate to jest skill
  • React component queries -- delegate to react-testing-library skill
  • Integration or E2E test strategy (different scope)

Critical Patterns

AAA Pattern (Arrange-Act-Assert)

Every test follows three phases separated by blank lines.

// CORRECT: clear AAA separation
it('should apply discount', () => {
  const order = createOrder({ subtotal: 200 });       // Arrange
  const result = new PercentDiscount(10).apply(order); // Act
  expect(result.total).toBe(180);                      // Assert
});
// WRONG: everything crammed, hard to read
it('discount', () => {
  expect(new PercentDiscount(10).apply(createOrder({ subtotal: 200 })).total).toBe(180);
});

Test Isolation

// CORRECT: fresh state per test
let repo: jest.Mocked<UserRepo>;
let service: UserService;
beforeEach(() => {
  repo = { findById: jest.fn(), save: jest.fn() } as any;
  service = new UserService(repo);
});
// WRONG: mutated across tests, order-dependent
const users: User[] = [];
it('adds', () => { users.push(newUser); });
it('checks', () => { expect(users).toHaveLength(1); });

One Assertion per Concept + Naming

Multiple expect calls are fine if they assert facets of the same outcome. Name tests as specs: should <expected> when <condition>.

// CORRECT: one concept, descriptive name
it('should create user with defaults', () => {
  const user = service.create({ name: 'Ada' });
  expect(user.name).toBe('Ada');
  expect(user.role).toBe('member');
});
// WRONG: vague name, unrelated behaviors
it('create and delete', () => { /* two concerns */ });

Mock Boundaries (Mock I/O, Not Logic)

// CORRECT: mock the repo (I/O), test real service logic
repo.findById.mockResolvedValue({ id: '1', balance: 50 });
const result = await service.withdraw('1', 30);
expect(result.balance).toBe(20);
// WRONG: mocking the unit you are testing
jest.spyOn(service, 'withdraw').mockResolvedValue({ balance: 20 });

Positive and Negative Assertions

Every behavior has two sides: what SHOULD happen (positive) and what SHOULD NOT (negative). A test suite that only checks positive paths misses entire failure categories. Test both explicitly.

// ✅ POSITIVE: expected outcome occurs
expect(user.role).toBe('member');
expect(repo.save).toHaveBeenCalledWith(expect.objectContaining({ name: 'Ada' }));

// ✅ NEGATIVE: invalid input is rejected, wrong state is absent
expect(() => service.create({ name: '' })).toThrow('Name required');
await expect(service.withdraw('1', 9999)).rejects.toThrow('Insufficient funds');
expect(result.errors).not.toContain('email'); // valid field must not appear in errors
expect(repo.save).not.toHaveBeenCalled();     // no side-effect on failure

Negative assertions use Jest matchers — see jest skill for .not, .toThrow(), .rejects.


Decision Tree

React component?
  → Delegate to react-testing-library skill

Pure function?
  → No mocks needed; call and assert

Service with dependencies?
  → Mock I/O via jest skill

Need setup per test?
  → beforeEach + afterEach

Multiple behaviors?
  → One it per behavior under describe

Error handling?
  → Dedicated it('should throw when ...') case

Unsure what to mock?
  → Mock anything touching network, disk, or clock

Example

import { AccountService } from './accountService';
import type { AccountRepo } from '../repositories/accountRepo';

describe('AccountService.withdraw', () => {
  let repo: jest.Mocked<AccountRepo>;
  let service: AccountService;
  beforeEach(() => {
    repo = { findById: jest.fn(), save: jest.fn() } as any;
    service = new AccountService(repo);
  });
  it('should deduct and save', async () => {
    repo.findById.mockResolvedValue({ id: '1', balance: 100 });
    repo.save.mockResolvedValue({ id: '1', balance: 70 });
    const account = await service.withdraw('1', 30);
    expect(account.balance).toBe(70);
  });
  it('should throw when balance insufficient', async () => {
    repo.findById.mockResolvedValue({ id: '1', balance: 10 });
    await expect(service.withdraw('1', 50)).rejects.toThrow('Insufficient funds');
  });
});

Edge Cases

  • Flaky async: Always await async operations; use fake timers for time-dependent logic
  • Coverage gaps: Write explicit tests for else, catch, and default branches
  • Test coupling: If renaming private method breaks tests, test public API only
  • Shared utilities: Extract factories (createUser()) into test/helpers/
  • Non-deterministic data: Seed random values or freeze Date.now()

Checklist

  • Every test follows AAA with clear phase separation
  • No test depends on order or outcome of another test
  • Mocks limited to I/O boundaries (repos, HTTP, filesystem)
  • Each it has a descriptive should ... when ... name
  • beforeEach creates fresh instances; afterEach restores mocks
  • Error and edge-case paths have dedicated test cases
  • Test file lives next to source: <module>.test.ts

Resources