Skip to content

Comparison with Behat ​

If you're familiar with Behat, you'll find many similarities in Pest BDD. However, there are significant differences in philosophy and implementation. This guide helps you understand these differences and migrate existing knowledge.

Feature Comparison ​

FeatureBehatPest BDD
Gherkin SupportFullFull (via behat/gherkin)
Configurationbehat.yml requiredZero config
Step Binding@Given annotations#[Given] attributes
Context ClassesFeatureContext requiredAny class works
Test RunnerStandalone behat commandIntegrated with Pest
AssertionsPHPUnit assertionsPest expect()
Parallel ExecutionExtension requiredBuilt-in (--parallel)
Laravel Integrationbehat/mink-extensionNative factory support
PHP VersionPHP 7.2+PHP 8.1+
Languages70+70+ (same parser)

Configuration ​

Behat ​

Behat requires a behat.yml configuration file:

yaml
# behat.yml
default:
  suites:
    default:
      contexts:
        - FeatureContext
      paths:
        - features

  extensions:
    Behat\MinkExtension:
      base_url: http://localhost
      sessions:
        default:
          goutte: ~

Pest BDD ​

Pest BDD requires no configuration:

bash
# Just run it
pest --bdd

Features are discovered in tests/Behaviors/ and step definitions are found via PSR-4 namespace scanning.

Step Definitions ​

Behat ​

php
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;

class FeatureContext implements Context
{
    private int $number;

    /**
     * @Given I have number :n
     */
    public function iHaveNumber(int $n): void
    {
        $this->number = $n;
    }

    /**
     * @When I add :n
     */
    public function iAdd(int $n): void
    {
        $this->number += $n;
    }

    /**
     * @Then the result should be :expected
     */
    public function theResultShouldBe(int $expected): void
    {
        if ($this->number !== $expected) {
            throw new \Exception("Expected $expected but got {$this->number}");
        }
    }
}

Pest BDD ​

php
// tests/Assertions/CalculatorSteps.php
use TestFlowLabs\PestTestAttributes\Given;
use TestFlowLabs\PestTestAttributes\Then;
use TestFlowLabs\PestTestAttributes\When;

class CalculatorSteps
{
    private int $number;

    #[Given('I have number {n}')]
    public function haveNumber(int $n): void
    {
        $this->number = $n;
    }

    #[When('I add {n}')]
    public function add(int $n): void
    {
        $this->number += $n;
    }

    #[Then('the result should be {expected}')]
    public function resultShouldBe(int $expected): void
    {
        expect($this->number)->toBe($expected);
    }
}

Key Differences ​

  1. Annotations vs Attributes: Behat uses docblock annotations (@Given), Pest BDD uses PHP 8 attributes (#[Given])

  2. No Interface Required: Pest BDD classes don't need to implement any interface

  3. Placeholder Syntax: Behat uses :param, Pest BDD uses {param}

  4. Assertions: Pest BDD leverages Pest's expect() for fluent assertions

Parameter Types ​

Behat Transformations ​

php
// Behat requires explicit transformers
/**
 * @Transform :user
 */
public function castUser(string $username): User
{
    return User::where('username', $username)->first();
}

/**
 * @Given :user is logged in
 */
public function userIsLoggedIn(User $user): void
{
    $this->actingAs($user);
}

Pest BDD Type Inference ​

php
// Pest BDD infers types from method signatures
#[Given('user {name} exists')]
public function userExists(string $name): User
{
    return User::factory()->create(['name' => $name]);
}

#[When('the user logs in')]
public function userLogsIn(User $user): void // Auto-injected!
{
    $this->actingAs($user);
}

Pest BDD automatically:

  • Infers regex patterns from type hints
  • Stores return values in context
  • Injects matching types into subsequent steps

Context Sharing ​

Behat ​

php
// Behat uses constructor injection or shared contexts
class FeatureContext implements Context
{
    private SharedContext $shared;

    public function __construct(SharedContext $shared)
    {
        $this->shared = $shared;
    }
}

Pest BDD ​

php
// Pest BDD uses automatic context injection
#[Given('a user {name} exists')]
public function userExists(string $name): User
{
    return User::factory()->create(['name' => $name]);
}

#[Then('the user should be active')]
public function userShouldBeActive(User $user): void
{
    // $user automatically injected from previous step's return value
    expect($user->is_active)->toBeTrue();
}

Laravel Integration ​

Behat ​

Requires extensions and configuration:

yaml
# behat.yml
default:
  extensions:
    Laracasts\Behat\ServiceContainer\BehatExtension: ~
    Behat\MinkExtension:
      default_session: laravel
      laravel: ~
php
// FeatureContext.php
use Laracasts\Behat\Context\Migrator;
use Laracasts\Behat\Context\DatabaseTransactions;

class FeatureContext implements Context
{
    use Migrator, DatabaseTransactions;
}

Pest BDD ​

Native Laravel support with no extra configuration:

php
// database/factories/UserFactory.php
use TestFlowLabs\PestTestAttributes\Given;

class UserFactory extends Factory
{
    #[Given('a user exists')]
    public function definition(): array
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->email(),
        ];
    }

    #[Given('a user {name} exists')]
    public function withName(string $name): static
    {
        return $this->state(['name' => $name]);
    }

    #[Given('an admin user exists')]
    public function admin(): static
    {
        return $this->state(['role' => 'admin']);
    }
}

See Laravel Integration for more details.

Running Tests ​

Behat ​

bash
# Run all features
behat

# Run specific feature
behat features/calculator.feature

# Run with tags
behat --tags="@smoke"

# Run specific scenario
behat --name="Addition"

Pest BDD ​

bash
# Run all BDD features
pest --bdd

# Run with filter
pest --bdd --filter="Calculator"

# Run in parallel
pest --bdd --parallel

# Run with tags
pest --bdd --tags="@smoke"

Migration Guide ​

Step 1: Install Pest BDD ​

bash
composer require testflowlabs/pest-plugin-bdd --dev

Step 2: Move Feature Files ​

Move features from features/ to tests/Behaviors/:

bash
mv features/*.feature tests/Behaviors/

Step 3: Convert Step Definitions ​

Convert annotations to attributes and update placeholder syntax:

Before (Behat):

php
/**
 * @Given I have :count items
 */
public function iHaveItems(int $count): void

After (Pest BDD):

php
#[Given('I have {count} items')]
public function haveItems(int $count): void

Step 4: Update Assertions ​

Replace PHPUnit assertions with Pest expectations:

Before:

php
assertEquals($expected, $actual);
assertTrue($condition);
assertInstanceOf(User::class, $user);

After:

php
expect($actual)->toBe($expected);
expect($condition)->toBeTrue();
expect($user)->toBeInstanceOf(User::class);

Step 5: Remove Behat Configuration ​

Delete behat.yml and remove Behat dependencies:

bash
composer remove behat/behat behat/mink-extension
rm behat.yml

Step 6: Run Tests ​

bash
pest --bdd

Feature Parity ​

Available in Both ​

  • Gherkin syntax (Given/When/Then)
  • Background steps
  • Scenario Outlines with Examples
  • Tags and tag filtering
  • Multi-language support (70+ languages)
  • And/But conjunctions

Pest BDD Advantages ​

  • Zero configuration
  • Native PHP 8 attributes
  • Automatic type inference
  • Built-in context injection
  • Laravel factory integration
  • Integrated with Pest ecosystem
  • Parallel execution built-in

Behat Advantages ​

  • Mature ecosystem with many extensions
  • Mink integration for browser testing
  • More granular hooks system
  • Wider IDE support for annotations

Released under the MIT License.