For years, picking a BDD testing tool was a two-option decision: Cucumber if you wrote Java, JavaScript, or Ruby, and SpecFlow if you lived in .NET. That changed when SpecFlow reached end of life in December 2024, and newer stacks like Playwright and Go now have their own BDD options.
This guide compares the 10 BDD testing tools worth considering in 2026. Every tool runs the same example scenario, so you can compare the code side by side, and each entry ends with who it suits and what it costs you. If you are new to the practice itself, start with our guide to behaviour driven development.
Quick picks by language
- Java, JavaScript, or Ruby: Cucumber
- .NET and C#: Reqnroll
- Python: Behave or pytest-bdd
- PHP: Behat
- API-first teams on any stack: Karate
- Teams already using Playwright: playwright-bdd
- Go: Godog
What are BDD testing tools?
BDD testing tools are frameworks that turn plain-language scenarios into automated tests. Product owners, developers, and testers describe expected behavior in a structured format called Gherkin (Given, When, Then), and the tool links each line of that scenario to code that drives the application and checks the result.
Here is the scenario every tool in this guide implements:
Feature: Place an order
Scenario: Create an order via the API
Given a registered user
When they create an order for 2 items
Then the API responds with status 201
The feature file stays readable for everyone on the team. The code behind each step, called a step definition, is where the tools differ: each one targets a different language and test runner.

BDD builds on test-first development rather than replacing it. If you want to see how the two practices differ, read our breakdown of TDD vs BDD.
BDD testing tools compared
Maintenance status is based on each project’s latest public release as of October 2026.
| Tool | Language | Spec format | Best for | UI or API | License | Maintenance status |
|---|---|---|---|---|---|---|
| Cucumber | Java, JavaScript, TypeScript, Ruby, and more | Gherkin | Cross-language teams that want the largest ecosystem | Both | MIT | Active, releases in October 2026 |
| Reqnroll | C# and .NET | Gherkin | .NET teams and SpecFlow migrations | Both | BSD-3-Clause | Active, v3.3 in 2026 |
| Behave and pytest-bdd | Python | Gherkin | Python teams, standalone or inside pytest | Both | BSD-2-Clause and MIT | Active, pytest-bdd 9.0 in September 2026 |
| Karate | Runs on the JVM, tests any stack | Gherkin-style DSL | API testing with no step definitions | API first, also UI and performance | MIT | Active, v2.1 in September 2026 |
| Behat | PHP | Gherkin | Symfony, Laravel, and Drupal projects | Both | MIT | Active, v4.0 in September 2026 |
| Serenity BDD | Java | Gherkin or JUnit | Rich reports and living documentation | Both | Apache-2.0 | Active, v5.3 in July 2026 |
| Gauge | Java, JavaScript, Python, C#, Ruby | Markdown specs | Readable specs without strict Gherkin | Both | Apache-2.0 | Active, v1.6 in September 2026 |
| playwright-bdd | TypeScript and JavaScript | Gherkin | Teams already on Playwright Test | Both | MIT | Active, v9.2 in September 2026 |
| Godog | Go | Gherkin | Go services and APIs | Mostly API | MIT | Active, v0.16 in July 2026 |
| Keploy | Language-agnostic, works at the API layer | No Gherkin, tests recorded from real API traffic | API regression coverage alongside a BDD suite | API | Apache-2.0 | Active, releases in October 2026 |
The 10 best BDD testing tools in 2026
1. Cucumber
Cucumber is the reference implementation of BDD and the tool most teams mean when they say “BDD framework.” It reads Gherkin feature files and runs them through official implementations for the JVM, JavaScript, and Ruby, with community ports for many other languages.
- Official support for Java, Kotlin, JavaScript, TypeScript, and Ruby
- Cucumber Expressions for readable step patterns such as
{int}and{string} - Tags, hooks, and data tables for organizing large suites
- Plugins for JUnit, TestNG, Jest-style reporters, and most CI systems
// features/step_definitions/order.steps.js (Cucumber-JS)
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('node:assert');
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
Given('a registered user', function () {
this.token = 'test-user-token';
});
When('they create an order for {int} items', async function (quantity) {
this.response = await fetch(${BASE_URL}/orders, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: Bearer ${this.token},
},
body: JSON.stringify({ sku: 'BOOK-101', quantity }),
});
});
Then('the API responds with status {int}', function (status) {
assert.strictEqual(this.response.status, status);
});
Best for: teams that work across several languages or want the most tutorials, plugins, and community answers.
Trade-off: step definitions grow quickly in large suites, and keeping them reusable takes discipline. For a hands-on walkthrough, see our guide to BDD testing with Cucumber.js.
2. Reqnroll
Reqnroll is the open source successor to SpecFlow, started in early 2024 by SpecFlow’s original creator. It keeps SpecFlow’s binding model, so most projects migrate by swapping packages and namespaces.
- Works with NUnit, xUnit, and MSTest
- Supports Cucumber Expressions as well as regular expressions
- Maintained IDE extensions for Visual Studio and Rider
- Compatibility package for existing SpecFlow projects
// Steps/OrderSteps.cs (Reqnroll + NUnit)
using System.Net.Http.Json;
using NUnit.Framework;
using Reqnroll;
[Binding]
public class OrderSteps
{
private readonly HttpClient _client = new() { BaseAddress = new Uri("http://localhost:5000") };
private HttpResponseMessage? _response;
[Given("a registered user")]
public void GivenARegisteredUser()
{
_client.DefaultRequestHeaders.Add("Authorization", "Bearer test-user-token");
}
[When("they create an order for {int} items")]
public async Task WhenTheyCreateAnOrder(int quantity)
{
_response = await _client.PostAsJsonAsync("/orders", new { sku = "BOOK-101", quantity });
}
[Then("the API responds with status {int}")]
public void ThenTheApiRespondsWithStatus(int status)
{
Assert.That((int)_response!.StatusCode, Is.EqualTo(status));
}
}
Best for: any .NET team starting BDD today, and every team still running SpecFlow.
Trade-off: SpecFlow+ LivingDoc was closed source, so Reqnroll has no direct replacement for it. You will need a separate reporting plugin.
3. Behave and pytest-bdd
Python has two mature options. Behave is a standalone BDD runner with its own CLI and context object. pytest-bdd runs Gherkin scenarios as regular pytest tests, so it plugs into fixtures, plugins, and parallel runs you may already use.
- Both read standard Gherkin feature files
- Behave offers a simple, self-contained setup with environment hooks
- pytest-bdd reuses pytest fixtures and works with pytest-xdist and pytest-cov
- Both support step parameters with parse-style patterns
With Behave:
# features/steps/order_steps.py (Behave)
import requests
from behave import given, when, then
BASE_URL = "http://localhost:8000"
@given("a registered user")
def step_registered_user(context):
context.headers = {"Authorization": "Bearer test-user-token"}
@when("they create an order for {quantity:d} items")
def step_create_order(context, quantity):
context.response = requests.post(
f"{BASE_URL}/orders",
json={"sku": "BOOK-101", "quantity": quantity},
headers=context.headers,
)
@then("the API responds with status {status:d}")
def step_check_status(context, status):
assert context.response.status_code == status
With pytest-bdd:
# tests/test_orders.py (pytest-bdd)
import requests
from pytest_bdd import scenarios, given, when, then, parsers
scenarios("order.feature")
BASE_URL = "http://localhost:8000"
@given("a registered user", target_fixture="headers")
def headers():
return {"Authorization": "Bearer test-user-token"}
@when(parsers.parse("they create an order for {quantity:d} items"), target_fixture="response")
def create_order(headers, quantity):
return requests.post(
f"{BASE_URL}/orders",
json={"sku": "BOOK-101", "quantity": quantity},
headers=headers,
)
@then(parsers.parse("the API responds with status {status:d}"))
def check_status(response, status):
assert response.status_code == status
Best for: Behave suits teams that want BDD as its own suite. pytest-bdd suits teams that already run everything through pytest.
Trade-off: Behave releases less often than pytest-bdd, and mixing Behave with an existing pytest setup means running two test runners.
4. Karate
Karate takes a different approach: its Gherkin-style DSL already knows how to make HTTP calls and assert on JSON, so you write no step definitions at all. It runs on the JVM but can test APIs built in any language.
- Built-in HTTP, JSON, and XML handling
- Powerful
matchsyntax for response assertions - Parallel execution and HTML reports out of the box
- Also covers UI automation, mocks, and performance testing through Gatling
Feature: Place an order
Background:
* url 'http://localhost:8080'
* header Authorization = 'Bearer test-user-token'
Scenario: Create an order via the API
Given path 'orders'
And request { sku: 'BOOK-101', quantity: 2 }
When method post
Then status 201
And match response.id == '#notnull'
Best for: API-first teams that want BDD-style tests without a step definition layer.
Trade-off: scenarios read like test scripts rather than business language, so non-technical stakeholders tend to read them less. Teams with large API surfaces often pair a BDD framework with generated tests; our roundup of API testing tools covers the options.
5. Behat
Behat is the standard BDD framework for PHP. It reads Gherkin feature files and maps steps to methods in context classes, with extensions for browser testing through Mink and for frameworks like Symfony and Drupal.
- PHP 8 attributes for step definitions
- Turnip-style patterns such as
:quantityfor parameters - Mink extension for browser and headless testing
- Version 4.0 released in September 2026
<?php
// features/bootstrap/OrderContext.php (Behat)
use Behat\Behat\Context\Context;
use Behat\Step\Given;
use Behat\Step\Then;
use Behat\Step\When;
use GuzzleHttp\Client;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
class OrderContext implements Context
{
private Client $client;
private array $headers = [];
private ResponseInterface $response;
public function __construct()
{
$this->client = new Client(['base_uri' => 'http://localhost:8000', 'http_errors' => false]);
}
#[Given('a registered user')]
public function aRegisteredUser(): void
{
$this->headers = ['Authorization' => 'Bearer test-user-token'];
}
#[When('they create an order for :quantity items')]
public function theyCreateAnOrder(int $quantity): void
{
$this->response = $this->client->post('/orders', [
'headers' => $this->headers,
'json' => ['sku' => 'BOOK-101', 'quantity' => $quantity],
]);
}
#[Then('the API responds with status :status')]
public function theApiRespondsWithStatus(int $status): void
{
Assert::assertSame($status, $this->response->getStatusCode());
}
}
Best for: PHP teams, especially on Symfony, Laravel, or Drupal.
Trade-off: the ecosystem is PHP only, so polyglot teams end up maintaining a second BDD setup for other services.
6. Serenity BDD
Serenity BDD is a Java library that sits on top of Cucumber or JUnit and focuses on reporting. Its living documentation reports show which requirements are covered, which passed, and what each step did, with screenshots for UI tests.
- Detailed living documentation reports grouped by requirement
- Screenplay pattern for maintainable UI and API tests
- SerenityRest wrapper around REST Assured for API steps
- Integrates with Cucumber feature files or plain JUnit
// OrderSteps.java (Serenity BDD + Cucumber)
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import net.serenitybdd.rest.SerenityRest;
public class OrderSteps {
private static final String BASE_URL = "http://localhost:8080";
private String token;
@Given("a registered user")
public void aRegisteredUser() {
token = "test-user-token";
}
@When("they create an order for {int} items")
public void theyCreateAnOrder(int quantity) {
SerenityRest.given()
.baseUri(BASE_URL)
.header("Authorization", "Bearer " + token)
.contentType("application/json")
.body("{\"sku\":\"BOOK-101\",\"quantity\":" + quantity + "}")
.post("/orders");
}
@Then("the API responds with status {int}")
public void theApiRespondsWithStatus(int status) {
SerenityRest.then().statusCode(status);
}
}
Best for: Java teams that need reports stakeholders actually read.
Trade-off: setup and the Screenplay pattern have a learning curve, and the extra layer adds build time.
7. Gauge
Gauge is an open source framework from Thoughtworks that uses Markdown specifications instead of Gherkin. Specs read like documentation, and steps are implemented in Java, JavaScript, Python, C#, or Ruby.
- Markdown specs with no Given, When, Then requirement
- Language plugins for five major languages
- Parallel execution and HTML reports
- Data-driven specs using tables or CSV files
# Place an order
## Create an order via the API
* Register a user
* Create an order for "2" items
* The API responds with status "201"
// OrderSteps.java (Gauge)
import com.thoughtworks.gauge.Step;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class OrderSteps {
private static final HttpClient CLIENT = HttpClient.newHttpClient();
private String token;
private HttpResponse<String> response;
@Step("Register a user")
public void registerUser() {
token = "test-user-token";
}
@Step("Create an order for <quantity> items")
public void createOrder(String quantity) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/orders"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"sku\":\"BOOK-101\",\"quantity\":" + quantity + "}"))
.build();
response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
}
@Step("The API responds with status <status>")
public void checkStatus(String status) {
assertEquals(Integer.parseInt(status), response.statusCode());
}
}
Best for: teams that want readable specs but find strict Gherkin too rigid.
Trade-off: the community is smaller than Cucumber’s, and Markdown specs are not portable to Gherkin-based tools.
8. playwright-bdd
playwright-bdd converts Gherkin feature files into Playwright Test files, so BDD scenarios run with Playwright’s own runner, fixtures, parallelism, and trace viewer.
- Uses Playwright fixtures such as
pageandrequestinside steps - Full access to Playwright reports, traces, and sharding
- Supports Cucumber-style steps and decorator-based steps
- TypeScript first
// steps/order.steps.ts (playwright-bdd)
import { expect } from '@playwright/test';
import { createBdd } from 'playwright-bdd';
const { Given, When, Then } = createBdd();
let token: string;
let status: number;
Given('a registered user', async () => {
token = 'test-user-token';
});
When('they create an order for {int} items', async ({ request }, quantity: number) => {
const response = await request.post('/orders', {
headers: { Authorization: Bearer ${token} },
data: { sku: 'BOOK-101', quantity },
});
status = response.status();
});
Then('the API responds with status {int}', async ({}, expected: number) => {
expect(status).toBe(expected);
});
Best for: JavaScript and TypeScript teams that already use Playwright for end-to-end tests.
Trade-off: it depends on Playwright Test, so it is not a fit if you run your JavaScript tests on another runner.
9. Godog
Godog is the official Cucumber implementation for Go. Scenarios run through go test, which keeps BDD inside the standard Go toolchain.
- Maintained under the Cucumber organization
- Runs with
go testand integrates with existing Go test setups - Regular expression and Cucumber Expression step patterns
- Scenario hooks for setup and teardown
// orders_test.go (Godog)
package orders
import (
"bytes"
"fmt"
"net/http"
"testing"
"github.com/cucumber/godog"
)
type orderAPI struct {
token string
response *http.Response
}
func (a *orderAPI) aRegisteredUser() error {
a.token = "test-user-token"
return nil
}
func (a *orderAPI) theyCreateAnOrderForItems(quantity int) error {
body := fmt.Sprintf({"sku":"BOOK-101","quantity":%d}, quantity)
req, err := http.NewRequest(http.MethodPost, "http://localhost:8080/orders", bytes.NewBufferString(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+a.token)
req.Header.Set("Content-Type", "application/json")
a.response, err = http.DefaultClient.Do(req)
return err
}
func (a *orderAPI) theAPIRespondsWithStatus(status int) error {
if a.response.StatusCode != status {
return fmt.Errorf("expected status %d, got %d", status, a.response.StatusCode)
}
return nil
}
func InitializeScenario(sc *godog.ScenarioContext) {
api := &orderAPI{}
sc.Step(^a registered user$, api.aRegisteredUser)
sc.Step(^they create an order for (\d+) items$, api.theyCreateAnOrderForItems)
sc.Step(^the API responds with status (\d+)$, api.theAPIRespondsWithStatus)
}
func TestFeatures(t *testing.T) {
suite := godog.TestSuite{
ScenarioInitializer: InitializeScenario,
Options: &godog.Options{Format: "pretty", Paths: []string{"features"}, TestingT: t},
}
if suite.Run() != 0 {
t.Fatal("feature tests failed")
}
}
Best for: Go teams that want behavior specs for services and APIs.
Trade-off: still pre-1.0, and the ecosystem of plugins and reporters is small compared with Cucumber-JVM or Cucumber-JS.
10. Keploy
Keploy is not a Gherkin framework, and it does not replace one. It is an open source API testing tool that records real API traffic using eBPF and turns it into test cases and dependency mocks, with no code changes to your application. It belongs on this list because it solves the problem every tool above shares: hand-written step definitions take time to write and maintain, so most BDD suites only cover a small slice of the API.
- Generates API tests and mocks from real traffic through record and replay testing
- Works with any language, because it captures calls at the network layer
- Mocks databases and downstream services during replay, so tests stay fast and stable
- Runs in CI next to your BDD suite on every pull request
# Record: run the app through Keploy
keploy record -c "npm start"
# In another terminal, call the API as usual
curl -X POST http://localhost:3000/orders \
-H "Content-Type: application/json" \
-d '{"sku":"BOOK-101","quantity":2}'
# Replay the captured calls against a new build, with dependencies mocked
keploy test -c "npm start" --delay 10
Write scenarios for the flows stakeholders care about, and record regression tests for the rest of the API.
Best for: teams that use a BDD framework for business-critical scenarios and want API regression testing across the rest of the service without writing more step code. Our walkthrough on testing a web app with Cucumber.js and Keploy shows the two working together.
Trade-off: Keploy does not produce business-readable specifications, and it only covers the API layer. Keep a BDD tool for the scenarios you agree on with stakeholders.
What happened to SpecFlow?
Tricentis, which owned SpecFlow, announced its end of life in December 2024, and the project reached end of life on December 31, 2024. According to the Reqnroll team’s announcement, the SpecFlow GitHub projects were deleted at the start of 2025 and the support section of specflow.org was disabled. Existing SpecFlow projects still run, but they receive no fixes, and the IDE extensions are frozen.
Reqnroll is the maintained path forward. It started as a fork of SpecFlow in early 2024 and is designed for straightforward migration:
- Replace the SpecFlow NuGet packages with their Reqnroll equivalents, for example
Reqnroll.NUnitorReqnroll.xUnit - Change
using TechTalk.SpecFlow;tousing Reqnroll; - Install the Reqnroll extension for Visual Studio or Rider
- Move the whole solution at once, because mixing SpecFlow and Reqnroll runners causes conflicts
- Plan a replacement for SpecFlow+ LivingDoc, which has no direct Reqnroll equivalent
The official Reqnroll migration guide covers edge cases such as custom plugins and configuration files.
BDD tools to avoid for new projects
Several tools still appear in BDD tool lists but are not a good starting point in 2026:
- SpecFlow: end of life since December 2024. Use Reqnroll instead.
- JBehave: its last core release was in September 2023. Existing suites can stay, but new Java projects are better served by Cucumber-JVM or Serenity BDD.
- Lettuce: an early Python BDD tool that has been inactive for years. Use Behave or pytest-bdd.
- JDave: an old Java specification framework with no recent development.
- Concordion and FitNesse: both still work for teams that already rely on them, but neither is a natural choice for a new Gherkin-based suite.
How to choose the right BDD testing tool

Match your language and test runner
The biggest factor is the language your developers already use for tests. A BDD tool that runs inside your existing runner (pytest, JUnit, NUnit, Playwright Test, or go test) inherits your fixtures, CI setup, and reporting. A separate runner adds a second pipeline to maintain.
Decide between UI and API focus
For browser flows, tools with strong UI integrations such as playwright-bdd, Serenity BDD, or Behat with Mink are the better fit. For services and APIs, Karate removes the step definition layer entirely, and Cucumber, Reqnroll, or Godog work well with a plain HTTP client. If you need wide regression coverage across many endpoints, pairing your framework with generated tests saves the most effort.
Consider who maintains the step definitions
BDD only pays off when someone keeps feature files and step code in sync. If QA engineers own the suite, a lower-code option like Karate may suit them. If developers own it, pick the tool that matches their language so step definitions get the same review as application code.
Check CI/CD and reporting needs
Every tool here runs in CI, but reporting differs. Serenity BDD and Gauge produce rich HTML reports on their own, while Cucumber and Reqnroll rely on plugins. If stakeholders review results, test the report output before you commit to a tool.
Common BDD testing mistakes
- Writing imperative scenarios. Steps like “click the submit button” describe the interface, not the behavior. Write “When they place an order” and keep UI details in step definitions.
- Letting step definitions sprawl. Near-duplicate steps such as “a user exists” and “a registered user exists” multiply quickly. Review the step library the same way you review application code.
- Treating BDD as only test automation. The value comes from agreeing on scenarios with product and QA before coding. Feature files written after the fact rarely get read.
- Skipping a tagging strategy. Without tags like
@smokeor@regression, every pipeline runs every scenario, and CI times grow with the suite.
Conclusion
The best BDD testing tool is usually the one that fits the language and test runner your team already uses: Cucumber for Java, JavaScript, and Ruby, Reqnroll for .NET, Behave or pytest-bdd for Python, Behat for PHP, playwright-bdd for Playwright users, and Godog for Go. Karate is the strongest choice when APIs come first. Whichever framework you choose, keep hand-written scenarios for the behavior that matters most to the business, and use Keploy to cover the rest of your API with tests recorded from real traffic.
See how Keploy generates API tests from real traffic.
FAQs
What are BDD testing tools?
BDD testing tools are frameworks that run plain-language scenarios, usually written in Gherkin with Given, When, and Then steps, as automated tests. They connect each step to code that drives the application and checks the result, so the whole team can read what is being tested.
What is the most popular BDD testing tool?
Cucumber is the most widely used BDD testing tool. It has official implementations for Java, JavaScript, TypeScript, and Ruby, the largest plugin ecosystem, and the most learning resources. Godog is also maintained under the Cucumber organization for Go.
Is SpecFlow still supported?
No. SpecFlow reached end of life on December 31, 2024, and its GitHub projects were removed in early 2025. Reqnroll, a fork started by SpecFlow’s original creator, is the maintained replacement and supports migration from SpecFlow projects.
Which BDD tool is best for Python?
Behave and pytest-bdd are the two main options. Behave is a standalone runner with a simple setup. pytest-bdd runs scenarios inside pytest, which makes it the better choice if your team already uses pytest fixtures and plugins.
Which BDD tool is best for .NET?
Reqnroll is the best choice for .NET in 2026. It is the actively maintained successor to SpecFlow, works with NUnit, xUnit, and MSTest, and has maintained extensions for Visual Studio and Rider.
Can BDD tools be used for API testing?
Yes. Karate is built specifically for API testing with a Gherkin-style syntax, and Cucumber, Reqnroll, Behave, and Godog can test APIs through an HTTP client in their step definitions. Many teams also add generated API regression tests to cover endpoints their BDD scenarios do not reach.
What is the difference between BDD and TDD?
TDD focuses on developers writing unit tests before code to check technical correctness. BDD focuses on describing behavior in plain language that developers, testers, and business stakeholders agree on before development. Many teams use both, with TDD at the unit level and BDD for features.
Is Cucumber free?
Yes. Cucumber is open source under the MIT license and free to use. Some related commercial products, such as test management and reporting platforms, are paid, but the Cucumber framework itself costs nothing.

Leave a Reply