Blog
BDD Test Cases: Practical Examples for QA Teams
Write BDD test cases that hold up in review: Given/When/Then patterns, Scenario Outline examples, mapping to classic cases, and the anti-patterns to avoid.
- bdd
- gherkin
- test-cases
- test-design
- qa
BDD test cases are behavior descriptions in a structured Given/When/Then form that a product owner can read and an automation engineer can execute. Done well, they collapse the gap between what the team agreed a feature does and what the suite actually verifies. Done badly, they are a slower, more ceremonial way to write the brittle click scripts you already had, plus a step-definition layer to maintain.
This guide assumes you know how to write QA test cases and want the behavior-driven projection of that skill: what belongs in a scenario, when Scenario Outline earns its keep, how negatives and edges fit, and which habits rot a feature file. Scenarios start from agreed rules, so it pairs closely with acceptance criteria examples.
What a BDD test case actually is
A BDD test case is a single scenario: one named behavior expressed as a starting state, a triggering action, and an observable outcome.
Scenario: Reserving the last available unit marks the SKU out of stock
Given SKU "TENT-4P" has 1 unit available in warehouse "EU-1"
When a customer reserves 1 unit of "TENT-4P"
Then the reservation succeeds
And "TENT-4P" shows 0 units available in "EU-1"
And the SKU is flagged out of stock in the catalogThree things make this a test case rather than a wish. The Given names concrete state, the When names exactly one action, and the Then names outcomes a second engineer can mark failed without arguing about taste. Strip any of the three and you have documentation, not verification. The scenario name carries weight too: Scenario: Reserve stock tells a release manager nothing when it fails, while the name above tells them which rule broke.
When BDD test cases are worth it
BDD carries real cost: a shared step vocabulary, a step-definition layer, and the discipline to keep both from sprawling. Pay it where the return is highest.
| Situation | Fit | Why |
|---|---|---|
| Rules are debated between product, dev, and QA | Strong | Scenarios force the disagreement open before code |
| Stateful workflows: approvals, reservations, lifecycles | Strong | Given/When/Then maps onto state transitions |
| Non-engineers actually read the tests | Strong | The plain-language layer has a real audience |
| Pixel or layout verification | Poor | Gherkin cannot express visual intent usefully |
| Large input validation matrices | Poor | Faster and clearer as a data-driven unit test |
| Exploratory investigation | Poor | Charters, not scenarios |
The honest test is the reader question: if nobody outside QA ever opens the feature file, you are maintaining a translation layer with no audience.
Given, When, Then — the rules that survive review
Given describes state, not actions
Givens are preconditions that already hold. The moment a Given contains a click, it has become a step and the scenario loses its "start here" clarity.
- Good:
Given an approver is signed in with role "Finance Lead" - Weak:
Given the user opens the login page and enters valid credentials and clicks Sign in
The weak version also couples every scenario to the login UI. When a redesign moves that button, hundreds of unrelated scenarios fail for a reason none of them are about. Set state through the fastest reliable route — API seeding, fixtures, session injection — and reserve UI interaction for the behavior under test.
When is exactly one triggering action
Three Whens means three scenarios. Multiple triggers make failures ambiguous: you cannot tell which action broke without reading the trace. The exception is a genuinely atomic action such as "submits the form with two attachments" — one user intent, not two.
Then states observable outcomes
Each Then is an assertion someone can check. "Then the system works correctly" is not checkable. "Then the reservation appears in the approver queue with status Pending" is. Include the negative half of the outcome when it matters: a scenario that asserts an error message but never asserts that nothing persisted will pass against a bug that shows a friendly warning while writing the record anyway.
Worked example: document approval workflow
In this contract approval feature, a submitter sends a document for approval, a finance lead approves or rejects it, and approved documents lock for editing.
Feature: Contract approval
Background:
Given contract "C-4471" exists in status "Draft"
And "dana@example.com" has role "Submitter"
And "leo@example.com" has role "Finance Lead"
Scenario: Submitting a draft moves it to the approver queue
Given "dana@example.com" is signed in
When she submits contract "C-4471" for approval
Then contract "C-4471" has status "Pending approval"
And "leo@example.com" sees "C-4471" in the approval queue
And the contract is read-only for "dana@example.com"
Scenario: Approving a contract locks it and records the approver
Given contract "C-4471" has status "Pending approval"
And "leo@example.com" is signed in
When he approves contract "C-4471"
Then contract "C-4471" has status "Approved"
And the approval history records "leo@example.com" with a timestampNote what the Background does not hold. It carries facts true for every scenario — the contract and the two roles — but not "is signed in," because the acting user differs per scenario. A Background that quietly logs someone in is a common source of confusing feature files: readers stop being able to tell who is acting.
Scenario Outline: parameterizing without duplicating
Scenario Outline exists for one job: the same behavior across different data.
Scenario Outline: Approval limits depend on the approver's spending authority
Given contract "C-9000" has a total value of <value>
And "<approver>" has an approval limit of <limit>
When "<approver>" attempts to approve contract "C-9000"
Then the result is "<outcome>"
Examples:
| approver | limit | value | outcome |
| leo@example.com | 50000 | 49999 | approved |
| leo@example.com | 50000 | 50000 | approved |
| leo@example.com | 50000 | 50001 | escalated to Director |Three habits keep outlines useful. Every row must exercise the same rule — here, value against limit. Boundary rows belong in the table deliberately: at the limit, one below, one above. And the table should fit on one screen; a forty-row outline is a data-driven test smuggled into a feature file. When rows start needing different Then clauses, split them into separate scenarios.
Mapping BDD scenarios to classic test cases
BDD is a projection of the same underlying case, not a rival suite. An explicit mapping prevents the two-sources-of-truth problem that eventually forces teams to abandon one view.
| Classic field | BDD equivalent | Notes |
|---|---|---|
| ID | Tag such as @CONTRACT-118 | Keep one canonical ID across both views |
| Title | Scenario name | Same standard: behavior plus outcome |
| Preconditions | Given / Background | Background only for file-wide facts |
| Steps | When | One action per scenario |
| Expected result | Then / And | Each assertion separately falsifiable |
| Type and priority | Tags such as @negative, @smoke | Drives filtered CI runs |
Treat Gherkin as the executable view of that record. The QA test case template covers the field definitions worth keeping stable across both.
Negative scenarios in BDD
Behavior includes refusal, and a feature file that only describes success is describing a demo. The patterns in negative test cases apply directly; they just get a Gherkin body.
@negative
Scenario: A submitter cannot approve their own contract
Given contract "C-4471" has status "Pending approval"
And "dana@example.com" submitted contract "C-4471"
And "dana@example.com" is signed in
When she attempts to approve contract "C-4471"
Then the approval is rejected with reason "self-approval not permitted"
And contract "C-4471" still has status "Pending approval"
And no entry is added to the approval historyThe last two Thens carry most of the value: they prove the refusal was real rather than cosmetic. A scenario ending at "then an error is shown" passes against the exact bug worth catching — an error banner rendered on top of a completed state change. Four negatives almost always earn their slot in a workflow feature: acting from a terminal state, acting without the required role, acting on a record someone else resolved, and replaying a stale action from a second tab.
Edge scenarios in BDD
Edges are valid but unusual, and Gherkin suits them because the unusual condition sits in the Given where a reader can see it. The selection logic in edge case testing tells you which ones earn a slot.
@edge
Scenario: Two approvers acting within the same second produce one approval
Given contract "C-4471" has status "Pending approval"
And "leo@example.com" and "mia@example.com" both have sufficient authority
When both approve contract "C-4471" concurrently
Then exactly one approval is recorded
And the second request is rejected as already resolved
And the approval history shows a single approverConcurrency, boundary values, timezone-sensitive deadlines, and records at a quota limit are the edge families most workflow features need — each deserving an explicit scenario rather than a hopeful comment in the story.
Step-by-step: turning a story into BDD test cases
- Extract the rules. List every rule in the acceptance criteria as a sentence: "Only a Finance Lead can approve." "Approved contracts are read-only." Rules, not screens.
- Name one scenario per rule, before writing bodies. If a name needs "and" to describe its outcome, it is two scenarios.
- Decide the Given depth. Choose the cheapest reliable route to the starting state — API or fixture setup over UI walkthroughs.
- Write the single When as user intent, not widget: "submits for approval," not "clicks the blue button."
- Write falsifiable Thens covering state, side effects, and what must not have changed.
- Add the negative twin. Every rule granting permission or enforcing a limit gets its refusal scenario immediately.
- Collapse duplicates into an outline only when the rule is identical and just the data varies, then tag each scenario with type, priority, and story ID so CI can run a meaningful subset.
- Review with a non-tester, then automate. If product cannot confirm a scenario matches intent, fix the wording before anyone writes step definitions.
Common anti-patterns
- Imperative UI scripts in Gherkin. "Given I click Settings, And I click Users, And I click Add" is a click script with extra syntax. Describe intent; let the step definition own the clicks.
- Technical leakage. Selectors, database tables, and status codes in the Gherkin break the promise that a non-engineer can read the file.
- Scenario chaining. A scenario depending on state left by the previous one makes the suite order-dependent and unrunnable in parallel.
- Step explosion. Ten near-identical phrasings of "the user is signed in." Without a shared vocabulary, the glue layer becomes the maintenance burden BDD was supposed to remove.
- Vague Thens and dead files. "Then it works" survives review far more often than it should, and scenarios kept after their behavior was removed teach the team to skip the suite wholesale.
Best practices for step definitions and reuse
Keep the glue layer thin. A step definition translates a sentence into one call against a service or page object; when a step needs an if, the branch belongs in a separate scenario.
| Practice | Effect |
|---|---|
| One shared vocabulary file per domain concept | Prevents six phrasings of the same step |
| Parameterize nouns, not verbs | Given contract "C-4471" reuses; a hard-coded submitter name does not |
| Reset state per scenario | Enables parallel runs, removes order coupling |
| Fail with the business sentence, not a stack trace | Failure output names the rule that broke |
Audit unused step definitions periodically; it is the fastest way to find feature files that stopped meaning anything. For CI, keep a small @smoke set covering the primary path and the highest-risk refusal, a regression set covering every documented rule once, and a nightly pack for edges. Depth trade-offs are covered in Basic, Standard, or Deep coverage; plan limits for generated drafts are on pricing.
Review checklist
- Every scenario name states one behavior and its outcome
- Given contains state, not user actions
- Exactly one When per scenario
- Every Then is observable and falsifiable
- Refusal scenarios assert that nothing persisted
- Background holds only file-wide facts, never a sign-in
- Outline rows exercise one rule, including boundaries
- No selectors or status codes in the Gherkin layer
- Scenarios are independent and safe to run in parallel
- Tags carry ID, type, and priority for CI selection
- Dead scenarios and orphan step definitions are removed
FAQ
Is BDD a testing technique or a collaboration practice?
Primarily a collaboration practice. The conversation between product, development, and QA is where most of the value lands; the feature file is what that conversation leaves behind. Adopting the syntax without the conversation buys the maintenance cost and none of the shared understanding.
Do we need Cucumber to write BDD test cases?
No. The Given/When/Then discipline works in a document, a ticket, or a test management tool. A runner is only needed when you want the scenarios executed directly.
Should BDD scenarios replace our existing test cases?
Treat them as one view of a single record rather than a second suite. Keep one canonical case ID and let Gherkin be the executable projection. Two independently maintained suites drift within a quarter, and nobody notices until a rule changes in one of them.
How many scenarios should a feature file have?
Enough to cover each documented rule once, plus the high-risk refusals and edges. Past roughly twenty scenarios, a file is usually describing more than one feature and should be split by capability.
Where do acceptance criteria end and scenarios begin?
Criteria state the rule; scenarios make it executable with concrete data. "Approvers cannot approve above their limit" becomes an outline with boundary rows. If the criteria are vague, the scenarios will be too — fix the criteria first.
How do we stop a BDD suite from getting slow?
Seed Givens through APIs rather than UI walkthroughs, and keep scenarios independent so they run in parallel. Most slow suites are slow because of setup, not assertions.
Summary
BDD test cases earn their place when they make a rule visible to everyone who has an opinion about it and executable by the people who verify it. The mechanics are short: state in the Given, one action in the When, falsifiable outcomes in the Then, refusals asserted down to persistence, outlines only for identical rules with varying data, and a thin glue layer over a shared vocabulary. Start with the rules in your next story, write one scenario per rule with its negative twin, and only then open the step definition file.
CTA — turn agreed rules into scenarios faster
The slow part of BDD is rarely the syntax; it is producing the first honest list of rules, refusals, and edges from a story that only describes the happy path. Draft that list in QA Workflow Assistant, then shape the wording with product before anyone writes a step definition. Runner and environment setup is in Docs.
Related articles
Acceptance Criteria Examples for QA and Product Teams
Acceptance criteria examples that survive refinement: good versus bad wording, rule-based and scenario formats, and how to convert criteria into test cases.
August 12, 2026 · 12 min read
Negative Test Cases: Examples Every QA Engineer Should Use
Learn how to design negative test cases with practical examples for validation, authorization, state conflicts, and API failures that catch real defects.
August 8, 2026 · 12 min read
QA Test Case Template (Free Examples + Best Practices)
Use a practical QA test case template with field definitions, filled examples, and team standards for consistent manual and automated testing.
August 2, 2026 · 13 min read