DEV Community

Cover image for Clear Tests SCREAM "AAA"! — Arrange, Act, Assert
Doogal Simpson
Doogal Simpson

Posted on • Originally published at doogal.dev

Clear Tests SCREAM "AAA"! — Arrange, Act, Assert

Most developers view Unit Tests as a chore—a bureaucratic box to tick so the Pull Request gets approved.

This mindset leads to "The Wall of Text" test: a 40-line function where mock data, execution logic, and assertions are blended into a grey soup. When the test fails three months later, nobody knows what it was supposed to do. They just know it’s red.

Tests are not just error catchers. Tests are Living Documentation.

Comments rot. Wiki pages get outdated. But if the test suite passes, it is the absolute truth of how the system works.

To keep this documentation readable, the Professional Junior adheres to one golden structural rule: The AAA Pattern.

The Concept: Visual Separation

We structure every single test into three distinct phases. We don’t just group them logically; we separate them visually with whitespace.

  1. Arrange: Set the stage. Create objects, mock the database, set the variables.
  2. Act: Trigger the specific function or behavior we are testing.
  3. Assert: Verify the result.

Your tests should scream this structure. If I open your test file, I should be able to scan down the page and see the rhythm of the logic without reading a single variable name.

The Code

Let’s look at a test for a refund processor.

The Junior Trap: The Wall of Text

The "Coder" writes tests to satisfy the compiler. They jam everything together to save vertical space.

// Before: The "Get It Over With" approach
// Good luck debugging this at 4 PM on a Friday.

test('refund logic', async () => {
  const pendingOrder = { id: 123, status: 'pending', amount: 50 };
  const mockDb = { getOrder: () => pendingOrder };
  const result = await processRefund(123, mockDb);
  expect(result).toBeDefined(); // Vague assertion
  expect(result.error).toBe('Refund failed: Order is pending');
});
Enter fullscreen mode Exit fullscreen mode

The Pro Move: The AAA Structure

The "Professional" writes tests for the human reader. We use blank lines to separate the "thoughts."

// After: The AAA Pattern
// This reads like a specification.

test('should throw error when refunding a pending order', async () => {
  // 1. ARRANGE
  // We explicitly set up the scenario.
  const pendingOrder = { id: 123, status: 'pending', amount: 50 };
  const mockDb = { getOrder: () => pendingOrder };

  // 2. ACT
  // We trigger the ONE thing we are testing.
  const result = processRefund(123, mockDb);

  // 3. ASSERT
  // We verify specific behavior.
  await expect(result)
    .rejects
    .toThrow('Refund failed: Order is pending');
});
Enter fullscreen mode Exit fullscreen mode

The Architectural Insight: "Hard to Test = Hard to Use"

The AAA pattern does more than make your code pretty; it acts as a Code Smell Detector.

Pay attention to the size of your Arrange section.

If you have to write 30 lines of code in the "Arrange" block just to trigger one line in the "Act" block, your code is screaming at you. It is saying: "I am too coupled."

[The Rule]: If it is hard to test, it is hard to use.

If it requires a degree in rocket science to mock the inputs for your function, it will be equally difficult for your teammates to use that function in the actual application.

The Fix

Don't just force the test to work. Listen to the signal.

  1. Stop writing the test.
  2. Refactor the code. Break the function down. Inject dependencies properly.
  3. Return to the test.

When the "Arrange" block becomes simple, your architecture has become clean.


Stop writing code just to please the compiler.

This article was an excerpt from my handbook, "The Professional Junior: Writing Code that Matters."

It’s not a 400-page textbook. It’s a tactical field guide to unwritten engineering rules.

👉 Get the Full Handbook Here

Top comments (0)