# Aaa Pattern

> Use when writing tests. Use when test structure is unclear. Use when arrange/act/assert phases are mixed.

- Skill: `yanko-belov/aaa-pattern` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yanko-belov/aaa-pattern`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yanko-belov/aaa-pattern/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yanko-belov (https://skillmd.com/u/yanko-belov)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yanko-belov/aaa-pattern

---


# AAA Pattern (Arrange-Act-Assert)

## Overview

**Every test has three phases: Arrange, Act, Assert. Keep them separate.**

Clear structure makes tests readable, maintainable, and debuggable. When phases blur together, tests become confusing.

## When to Use

- Writing any test
- Test logic is hard to follow
- Unclear what's being tested
- Multiple actions or assertions mixed together

## The Iron Rule

```
EVERY test must have clearly separated Arrange, Act, and Assert phases.
```

**No exceptions:**
- Not for "it's a simple test"
- Not for "it's more concise this way"
- Not for "the phases are obvious"

## Detection: Mixed Phases Smell

If arrange/act/assert blend together, STOP:

```typescript
// ❌ VIOLATION: Phases mixed together
it('adds items to cart', () => {
  const cart = new Cart();
  cart.add({ id: '1', price: 10 });
  expect(cart.items.length).toBe(1);  // Assert in the middle
  cart.add({ id: '2', price: 20 });   // More acting
  expect(cart.total).toBe(30);        // Another assert
  expect(cart.items.length).toBe(2);  // And another
});
```

Problems:
- What exactly is being tested?
- Which action caused which result?
- Hard to name the test accurately

## The Correct Pattern: Clear Separation

```typescript
// ✅ CORRECT: Clear AAA structure
it('calculates total price of all items in cart', () => {
  // Arrange
  const cart = new Cart();
  cart.add({ id: '1', name: 'Apple', price: 10 });
  cart.add({ id: '2', name: 'Banana', price: 20 });
  
  // Act
  const total = cart.getTotal();
  
  // Assert
  expect(total).toBe(30);
});

it('tracks number of items in cart', () => {
  // Arrange
  const cart = new Cart();
  
  // Act
  cart.add({ id: '1', name: 'Apple', price: 10 });
  cart.add({ id: '2', name: 'Banana', price: 20 });
  
  // Assert
  expect(cart.itemCount).toBe(2);
});
```

## The Three Phases

### Arrange
Set up the test scenario:
- Create objects
- Configure mocks
- Prepare input data
- Set initial state

### Act
Execute the behavior being tested:
- **Single action** (usually)
- Call the method
- Trigger the event
- Make the request

### Assert
Verify the outcome:
- Check return values
- Verify state changes
- Confirm mock interactions
- Validate side effects

## Advanced: Given-When-Then

For BDD-style tests, same concept:

```typescript
describe('Cart', () => {
  describe('when adding items', () => {
    it('should update the total', () => {
      // Given (Arrange)
      const cart = new Cart();
      const item = { id: '1', price: 25 };
      
      // When (Act)
      cart.add(item);
      
      // Then (Assert)
      expect(cart.total).toBe(25);
    });
  });
});
```

## One Assert Per Test?

**Guideline, not rule.** Multiple asserts are fine if they verify ONE behavior:

```typescript
// ✅ OK: Multiple asserts for one logical behavior
it('creates user with correct properties', () => {
  // Arrange
  const input = { email: 'a@b.com', name: 'Alice' };
  
  // Act
  const user = createUser(input);
  
  // Assert - all verify the creation behavior
  expect(user.id).toBeDefined();
  expect(user.email).toBe('a@b.com');
  expect(user.name).toBe('Alice');
  expect(user.createdAt).toBeInstanceOf(Date);
});
```

```typescript
// ❌ BAD: Multiple behaviors in one test
it('user operations', () => {
  const user = createUser({ name: 'Alice' });
  expect(user.name).toBe('Alice');
  
  updateUser(user.id, { name: 'Bob' });
  expect(user.name).toBe('Bob');  // Different behavior!
  
  deleteUser(user.id);
  expect(getUser(user.id)).toBeNull();  // Yet another behavior!
});
```

## Pressure Resistance Protocol

### 1. "It's More Concise"
**Pressure:** "Combining phases makes the test shorter"

**Response:** Short but confusing is worse than longer but clear.

**Action:** Separate the phases. Add comments if needed.

### 2. "It's a Simple Test"
**Pressure:** "For trivial tests, AAA is overkill"

**Response:** Consistency matters. All tests should follow the same pattern.

**Action:** Use AAA even for simple tests. It costs nothing.

### 3. "I Need Multiple Actions"
**Pressure:** "The behavior requires multiple steps"

**Response:** Multiple setup steps go in Arrange. Only the behavior being tested goes in Act.

**Action:** If you need multiple Acts, you probably need multiple tests.

## Red Flags - STOP and Reconsider

- `expect()` calls between actions
- No clear single "act" 
- Assertions scattered throughout
- Hard to describe what test verifies
- Test name doesn't match structure

**All of these mean: Restructure with clear AAA.**

## Quick Reference

| Phase | Contains | Example |
|-------|----------|---------|
| **Arrange** | Setup, mocks, data | `const cart = new Cart()` |
| **Act** | Single behavior | `const total = cart.checkout()` |
| **Assert** | Verifications | `expect(total).toBe(100)` |

## Common Rationalizations (All Invalid)

| Excuse | Reality |
|--------|---------|
| "It's more concise" | Clarity beats brevity. |
| "The phases are obvious" | Make them explicit anyway. |
| "Simple test, no need" | Consistency matters. |
| "Multiple actions needed" | Split into multiple tests. |
| "Comments are enough" | Structure is better than comments. |

## The Bottom Line

**Arrange. Act. Assert. In that order. Clearly separated.**

Every test sets up (Arrange), does one thing (Act), and verifies (Assert). When phases are clear, tests are readable, debuggable, and maintainable.

