Component-Level BDD vs Integration BDD — And Why BDD at All?
Why BDD in the First Place?
Behaviour Driven Development (BDD) is not just a testing technique — it’s a communication framework. It bridges the gap between business stakeholders, developers, and QA by expressing requirements in plain English using a shared language called Gherkin.
The core question BDD answers is:
“Does the software do what the business actually needs it to do?”
Written as:
Given a user is on the login page
When they enter valid credentials
Then they should be redirected to the dashboard
This isn’t just a test — it’s living documentation that non-technical stakeholders can read and verify.
Popular tools that support this approach include Behave (Python), Cucumber (Java/JS), SpecFlow (.NET), Playwright with Cucumber, and Cypress with the cypress-cucumber-preprocessor.
The BDD Pyramid: Where Does Each Layer Live?
▲
/ \
/ \ ← Integration BDD (E2E journeys, cross-service)
/─────\
/ \ ← Component BDD (isolated UI behaviour)
/─────────\
/ \ ← Unit (pure logic, no BDD needed typically)
/─────────────\
Component-Level BDD
Component BDD tests the behaviour of a single UI component in isolation — without routing, APIs, or the full app stack.
When to use it:
- A component has non-trivial conditional logic (show/hide, error states, loading states)
- The component is shared across multiple features or teams
- You want fast, focused feedback during development
Example — a SubmitButton component:
Feature: Submit Button
Scenario: Button is disabled while form is invalid
Given the form has missing required fields
Then the submit button should be disabled
Scenario: Button shows loading state after click
Given the form is valid
When the user clicks submit
Then the button should display a loading spinner
This can be implemented with any component-level testing tool — Cypress Component Testing, Playwright’s experimental component mode, or Storybook with Testing Library:
// Playwright / Testing Library style
test('is disabled when form is invalid', async ({ mount }) => {
const component = await mount(<SubmitButton isValid={false} />)
await expect(component.locator('button')).toBeDisabled()
})
# Behave step definition style
@then('the submit button should be disabled')
def step_impl(context):
button = context.browser.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
assert not button.is_enabled()
Benefits:
- Runs in milliseconds — no server, no DB
- Catches regressions at the source
- Makes component contracts explicit
Integration-Level BDD
Integration BDD tests end-to-end user journeys across multiple components, services, and APIs working together.
When to use it:
- Testing a complete user flow (sign up → onboard → purchase)
- Validating third-party integrations (payment gateway, auth provider)
- Acceptance criteria sign-off with stakeholders
Example — a checkout flow:
Feature: Checkout
Scenario: Successful purchase with saved card
Given the user has items in their basket
And they have a saved payment method
When they confirm the order
Then an order confirmation email should be sent
And the inventory count should decrease by 1
Tools like Playwright + Cucumber, Behave + Selenium, or Cypress + cucumber-preprocessor can drive these scenarios end-to-end.
Benefits:
- Validates the full system behaves as expected
- Stakeholders can read and approve scenarios
- Acts as regression safety net for cross-service behaviour
Component BDD vs Integration BDD — At a Glance
| Component BDD | Integration BDD | |
|---|---|---|
| Scope | Single component | Full user journey |
| Speed | Fast (ms) | Slower (seconds–minutes) |
| Dependencies | None (mocked) | Real services / APIs |
| Who writes it | Dev + QA | QA + BA + PO |
| Feedback loop | Immediate | Deferred |
| Tooling | Cypress CT, Playwright CT, Storybook | Playwright, Behave, Cypress, Selenium |
| Best for | UI logic, edge cases | Business flows, acceptance |
The Mistake Most Teams Make
Teams often skip component-level BDD and write everything as integration tests. This leads to:
- Slow pipelines — hundreds of E2E tests that take 30+ minutes
- Flaky tests — network timeouts, race conditions, environment drift
- Hard-to-debug failures — a failing E2E test could mean anything
The fix: Push behaviour verification down the pyramid. Test component behaviour at the component level, and reserve integration BDD for the journeys that genuinely require the full stack.
Practical Recommendation
- Use component BDD for anything that can be verified in isolation — state changes, conditional rendering, user interactions on a single component
- Use integration BDD for critical user journeys and stakeholder sign-off
- Aim for a ratio of roughly 3:1 — three component/unit-level BDD scenarios for every one integration scenario
Tool Selection Guide
| Need | Recommended Tools |
|---|---|
| Python-based BDD | Behave + Selenium or Playwright |
| JS/TS E2E BDD | Playwright + Cucumber-js, Cypress + cucumber-preprocessor |
| .NET BDD | SpecFlow + Playwright |
| Java BDD | Cucumber-JVM + Selenium or Playwright |
| Component isolation | Cypress Component Testing, Playwright CT, Storybook |
Final Thought
BDD’s real value isn’t the syntax — it’s the conversation it forces. When you write a Given/When/Then scenario, you’re asking: what does this actually need to do, and how do we know it’s done?
Whether at the component or integration level, and regardless of which tool your team has adopted, that question is always worth asking.
Written by Ilesh Darji — QA Engineer & Contractor