Skip to content

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.

QA Workflow Assistant11 min read
  • 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 catalog

Three 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.

SituationFitWhy
Rules are debated between product, dev, and QAStrongScenarios force the disagreement open before code
Stateful workflows: approvals, reservations, lifecyclesStrongGiven/When/Then maps onto state transitions
Non-engineers actually read the testsStrongThe plain-language layer has a real audience
Pixel or layout verificationPoorGherkin cannot express visual intent usefully
Large input validation matricesPoorFaster and clearer as a data-driven unit test
Exploratory investigationPoorCharters, 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 timestamp

Note 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 fieldBDD equivalentNotes
IDTag such as @CONTRACT-118Keep one canonical ID across both views
TitleScenario nameSame standard: behavior plus outcome
PreconditionsGiven / BackgroundBackground only for file-wide facts
StepsWhenOne action per scenario
Expected resultThen / AndEach assertion separately falsifiable
Type and priorityTags such as @negative, @smokeDrives 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 history

The 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 approver

Concurrency, 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

  1. 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.
  2. Name one scenario per rule, before writing bodies. If a name needs "and" to describe its outcome, it is two scenarios.
  3. Decide the Given depth. Choose the cheapest reliable route to the starting state — API or fixture setup over UI walkthroughs.
  4. Write the single When as user intent, not widget: "submits for approval," not "clicks the blue button."
  5. Write falsifiable Thens covering state, side effects, and what must not have changed.
  6. Add the negative twin. Every rule granting permission or enforcing a limit gets its refusal scenario immediately.
  7. 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.
  8. 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

  1. 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.
  2. Technical leakage. Selectors, database tables, and status codes in the Gherkin break the promise that a non-engineer can read the file.
  3. Scenario chaining. A scenario depending on state left by the previous one makes the suite order-dependent and unrunnable in parallel.
  4. 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.
  5. 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.

PracticeEffect
One shared vocabulary file per domain conceptPrevents six phrasings of the same step
Parameterize nouns, not verbsGiven contract "C-4471" reuses; a hard-coded submitter name does not
Reset state per scenarioEnables parallel runs, removes order coupling
Fail with the business sentence, not a stack traceFailure 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.