Given/When/Then Template for Specs
Given/When/Then is a practical grammar for behavior specifications. It reduces debate in reviews because each statement clearly identifies setup, trigger, and expected result.
Quick answer
Use Given to set up state and permissions, When for one trigger action, Then for verifiable outcomes. Keep scenarios independent so they can be tested in isolation and reused across automation suites.
Template rules
- Given must include actor, state, and preconditions.
- When should describe one user or system action only.
- Then should assert response, persistence, and side effects when relevant.
- Avoid implementation details such as class names or internal methods.
- One scenario should validate one behavior branch.
Reusable scenario set
# Scenario: Update email (happy path) - Given an authenticated user with edit permission - When user updates email to a unique valid value - Then return 200 and persist new email # Scenario: Duplicate email - Given an authenticated user - When user updates email to an existing value - Then return 409 with standardized conflict error # Scenario: Invalid format - Given an authenticated user - When user updates email with invalid format - Then return 400 validation error and keep old value # Scenario: Permission denied - Given a read-only role user - When user attempts to update email - Then return 403 and no data mutation occurs
Common writing mistakes
- Missing preconditions in Given, forcing implicit assumptions.
- Using vague Then phrases like "works correctly."
- Combining happy path and error path in one scenario.
- Writing steps that cannot be validated by tests.
If a scenario can't become a test without clarifying questions, rewrite it before implementation starts.
How to use in spec reviews
- Ask reviewers to challenge missing Given state assumptions.
- Ensure each Then maps to expected contract or UI behavior.
- Require negative and permission scenarios for risky flows.
- Link each scenario to test IDs during implementation planning.
Extended scenario patterns
The basic email-update example covers single-actor, synchronous interactions. Real systems also need patterns for asynchronous outcomes, multi-actor flows, and background job behavior. The structure stays the same — Given sets state, When triggers action, Then asserts observable outcome — but the scope of "observable" expands.
# Scenario: Async notification after order completion - Given an order with status = pending_fulfillment - When fulfillment service marks the order as shipped - Then a notification event is published to the customer.notifications topic And notification_log records entry with orderId and timestamp # Scenario: Multi-actor: admin overrides user lock - Given a user account locked after 5 failed login attempts - When an admin submits an account unlock with valid reason - Then user.status changes to active And audit log records admin ID, reason, and timestamp # Scenario: Background job idempotency - Given a scheduled invoice job runs with jobId = job-999 - When the job is triggered a second time with the same jobId - Then no duplicate invoices are created And second run returns early with logged skip event
Async scenarios should specify the observable artifact — a published event, a database record, a log entry — rather than the internal mechanism that produced it. If the Then clause requires knowledge of implementation internals to verify, rewrite it to a system-level observable.
When to split or merge scenarios
One of the most common structural mistakes is writing scenarios that cover multiple behavior branches. The rule is: one behavior branch per scenario. A scenario that tests both the happy path and the validation error in a single Given/When/Then block cannot be converted to a single test case without cheating — the test either misses a path or makes hidden assumptions.
Split a scenario when the Then clause contains two distinct verifiable assertions that could independently fail, or when the scenario title would require the word "and" to describe what it tests. Merge two scenarios when they share identical Given state and differ only in trivially equivalent inputs — for example, two equivalent valid formats that produce the same response. Keep scenarios separate when permission paths diverge; reader vs editor vs admin roles almost always need independent scenarios.
A quick rule for review: if a scenario cannot be phrased as a single declarative test name in your test framework, it is too broad. Rename it first — if the name requires a conjunction, split the scenario.
Mapping scenarios to automated tests
A scenario is only complete when it is linked to a test. The link does not have to be a test ID — it can be a file path, a test function name, or a Gherkin feature file location. What matters is that the traceability exists before implementation begins, not after.
- Given maps to fixture setup and database seed state.
- When maps to the request, event, or system action the test triggers.
- Then maps to assertions: HTTP status, response body fields, database state, emitted events.
- Each scenario should have at least one test that can fail independently of all others.
If the harness cannot express the Given state — because the fixture system does not support a required precondition — that is a signal to extend the harness, not to weaken the scenario. A scenario that is simplified to fit the test framework becomes unreliable specification.
Use these templates
Related articles
Editorial note
This guide covers the Given/When/Then Template for spec-first engineering teams. Examples are illustrative scenarios, not production code.
- Author details: Daniel Marsh
- Editorial policy: How we review and update articles
- Corrections: Contact the editor