# Arrange-Act-Assert (AAA)

## Problem

Tests become hard to read when setup, execution, and verification are interleaved. Reviewers can't quickly tell what is being tested, how, or what the expected outcome is. This leads to maintenance burden and missed bugs.

## Solution

Structure every test in exactly three phases:

1. **Arrange** — Set up the preconditions: create objects, configure stubs, prepare input data.
2. **Act** — Execute the single behaviour under test.
3. **Assert** — Verify the outcome: return values, state changes, or interactions.

Keep each phase visually separated (blank line or comment). The Act phase should be a single statement or very small cluster.

## When to Use

- Any unit or integration test in any language.
- When you want a consistent, scannable test format across a team.
- Code reviews benefit from immediately visible structure.

## When to Avoid

- Very trivial one-liner assertions where the structure adds noise.
- BDD-style acceptance tests where Given-When-Then is a better fit.
- Property-based tests where the framework generates arrange and assert.

## Pseudocode

```
function test_transfer_deducts_from_source():
    // Arrange
    source = Account(balance=100)
    target = Account(balance=50)

    // Act
    transfer(source, target, amount=30)

    // Assert
    assert source.balance == 70
    assert target.balance == 80
```

## Python

```python
"""Arrange-Act-Assert pattern in Python."""
from dataclasses import dataclass


@dataclass
class Account:
    owner: str
    balance: float

    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self.balance += amount

    def withdraw(self, amount: float) -> None:
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount


def transfer(source: Account, target: Account, amount: float) -> None:
    source.withdraw(amount)
    target.deposit(amount)


# ── Tests ────────────────────────────────────────────────────

def test_transfer_deducts_from_source():
    # Arrange
    source = Account(owner="Alice", balance=100.0)
    target = Account(owner="Bob", balance=50.0)

    # Act
    transfer(source, target, amount=30.0)

    # Assert
    assert source.balance == 70.0
    assert target.balance == 80.0
    print(f"PASS: source={source.balance}, target={target.balance}")


def test_transfer_insufficient_funds():
    # Arrange
    source = Account(owner="Alice", balance=10.0)
    target = Account(owner="Bob", balance=50.0)

    # Act & Assert
    try:
        transfer(source, target, amount=50.0)
        assert False, "Expected ValueError"
    except ValueError as e:
        assert str(e) == "Insufficient funds"
        print(f"PASS: got expected error: {e}")


def test_deposit_positive_amount():
    # Arrange
    account = Account(owner="Carol", balance=0.0)

    # Act
    account.deposit(25.0)

    # Assert
    assert account.balance == 25.0
    print(f"PASS: balance after deposit={account.balance}")


if __name__ == "__main__":
    test_transfer_deducts_from_source()
    test_transfer_insufficient_funds()
    test_deposit_positive_amount()
    print("\nAll AAA tests passed.")
```

## Go

```go
// arrange_act_assert_test.go
package main

import (
	"errors"
	"fmt"
)

// ── Production code ──────────────────────────────────────────

type Account struct {
	Owner   string
	Balance float64
}

func (a *Account) Deposit(amount float64) error {
	if amount <= 0 {
		return errors.New("deposit must be positive")
	}
	a.Balance += amount
	return nil
}

func (a *Account) Withdraw(amount float64) error {
	if amount > a.Balance {
		return errors.New("insufficient funds")
	}
	a.Balance -= amount
	return nil
}

func Transfer(source, target *Account, amount float64) error {
	if err := source.Withdraw(amount); err != nil {
		return err
	}
	return target.Deposit(amount)
}

// ── Tests ────────────────────────────────────────────────────

func testTransferDeductsFromSource() {
	// Arrange
	source := &Account{Owner: "Alice", Balance: 100.0}
	target := &Account{Owner: "Bob", Balance: 50.0}

	// Act
	err := Transfer(source, target, 30.0)

	// Assert
	if err != nil {
		panic(fmt.Sprintf("unexpected error: %v", err))
	}
	if source.Balance != 70.0 || target.Balance != 80.0 {
		panic("balance mismatch")
	}
	fmt.Printf("PASS: source=%.0f, target=%.0f\n", source.Balance, target.Balance)
}

func testTransferInsufficientFunds() {
	// Arrange
	source := &Account{Owner: "Alice", Balance: 10.0}
	target := &Account{Owner: "Bob", Balance: 50.0}

	// Act
	err := Transfer(source, target, 50.0)

	// Assert
	if err == nil {
		panic("expected error")
	}
	if err.Error() != "insufficient funds" {
		panic("wrong error message")
	}
	fmt.Printf("PASS: got expected error: %v\n", err)
}

func testDepositPositiveAmount() {
	// Arrange
	account := &Account{Owner: "Carol", Balance: 0.0}

	// Act
	err := account.Deposit(25.0)

	// Assert
	if err != nil {
		panic(fmt.Sprintf("unexpected error: %v", err))
	}
	if account.Balance != 25.0 {
		panic("balance mismatch")
	}
	fmt.Printf("PASS: balance after deposit=%.0f\n", account.Balance)
}

func main() {
	testTransferDeductsFromSource()
	testTransferInsufficientFunds()
	testDepositPositiveAmount()
	fmt.Println("\nAll AAA tests passed.")
}
```

## JavaScript

```javascript
// arrange_act_assert.js

class Account {
  constructor(owner, balance) {
    this.owner = owner;
    this.balance = balance;
  }

  deposit(amount) {
    if (amount <= 0) throw new Error("Deposit must be positive");
    this.balance += amount;
  }

  withdraw(amount) {
    if (amount > this.balance) throw new Error("Insufficient funds");
    this.balance -= amount;
  }
}

function transfer(source, target, amount) {
  source.withdraw(amount);
  target.deposit(amount);
}

// ── Tests ────────────────────────────────────────────────────

function testTransferDeductsFromSource() {
  // Arrange
  const source = new Account("Alice", 100);
  const target = new Account("Bob", 50);

  // Act
  transfer(source, target, 30);

  // Assert
  console.assert(source.balance === 70);
  console.assert(target.balance === 80);
  console.log(`PASS: source=${source.balance}, target=${target.balance}`);
}

function testTransferInsufficientFunds() {
  // Arrange
  const source = new Account("Alice", 10);
  const target = new Account("Bob", 50);

  // Act & Assert
  try {
    transfer(source, target, 50);
    throw new Error("Expected error");
  } catch (e) {
    console.assert(e.message === "Insufficient funds");
    console.log(`PASS: got expected error: ${e.message}`);
  }
}

function testDepositPositiveAmount() {
  // Arrange
  const account = new Account("Carol", 0);

  // Act
  account.deposit(25);

  // Assert
  console.assert(account.balance === 25);
  console.log(`PASS: balance after deposit=${account.balance}`);
}

testTransferDeductsFromSource();
testTransferInsufficientFunds();
testDepositPositiveAmount();
console.log("\nAll AAA tests passed.");
```

## Related Patterns

- **Given-When-Then** — Given-When-Then is the BDD equivalent of AAA; semantically identical but phrased for stakeholder readability.
- **Specification** — a specification pattern can formalise the assertions in the Assert phase.
- **Test Data Builder** — use a Test Data Builder to make the Arrange phase concise and expressive.
