Skip to content

Error Handling

Pest BDD provides detailed error messages to help you quickly identify and fix issues. This guide covers common errors and how to resolve them.

Step Not Found

When no step definition matches the step text:

StepNotFoundException: No step definition found for:
  "Given a user exist"

Did you mean?
  Given a user exists
  Given a user {name} exists

To create this step, add:

  #[Given('a user exist')]
  public function aUserExist(): void
  {
      // TODO: Implement step
  }

Causes

  1. Typo in step text

    gherkin
    Given a user exist    # Missing 's'
  2. Missing step definition

    php
    // Step not defined anywhere
  3. Step class not discovered

    bash
    # Run to update classmap
    composer dump-autoload --optimize
  4. Wrong step type

    gherkin
    When a user exists    # Should be Given

Solutions

  1. Fix the typo in your feature file
  2. Create the missing step definition
  3. Ensure step class is in an autoloaded directory
  4. Run composer dump-autoload --optimize

Parameter Resolution Error

When a step parameter can't be resolved:

ParameterResolutionException: Cannot resolve parameter $user in step:
  "When the user logs in"

Parameter details:
  Name: user
  Type: App\Models\User
  Position: 0

Resolution attempts:
  ✗ Alias 'user' not found in context
  ✗ Type 'App\Models\User' not found in context
  ✗ No extracted value for 'user'
  ✗ No default value

Available in context:
  Types:
    - App\Models\Order (from "Given an order exists")
    - App\Models\Product (from "Given a product exists")
  Aliases:
    - buyer (App\Models\User)
    - seller (App\Models\User)

Suggestion: Did you forget a Given step that creates a User?

Causes

  1. Missing prerequisite step

    gherkin
    # Missing: Given a user exists
    When the user logs in    # No User available!
  2. Alias mismatch

    gherkin
    Given a user exists as {admin}
    When the user logs in    # Expects $user, but alias is 'admin'
  3. Type mismatch

    php
    public function login(AdminUser $user): void
    // But only User was created, not AdminUser

Solutions

  1. Add the missing Given step:

    gherkin
    Given a user exists
    When the user logs in
  2. Match alias to parameter name:

    gherkin
    Given a user exists as {user}
    When the user logs in

    Or update step definition:

    php
    public function login(User $admin): void  // Match alias
  3. Return the correct type from Given step

Step Execution Error

When a step throws an exception:

StepExecutionException: Step failed during execution:
  "Then the order should be confirmed"

Error: Call to a member function fresh() on null

Step definition:
  Class: Tests\Assertions\Order\OrderAssertions
  Method: orderShouldBeConfirmed
  File: tests/Assertions/Order/OrderAssertions.php:45

Context at failure:
  Parameters received:
    - $order: App\Models\Order (id: 123)

  Available in context:
    - User (from "Given a user exists")
    - Order (from "When the user places an order")

Stack trace:
  #0 tests/Steps/OrderSteps.php(45): ...
  #1 src/Runner/ScenarioRunner.php(89): ...

Common Causes

  1. Null reference

    php
    #[Then('the order should be confirmed')]
    public function confirmed(Order $order): void
    {
        expect($order->fresh()->status)->toBe('confirmed');
        // $order->fresh() might return null!
    }
  2. Assertion failure

    php
    expect($order->status)->toBe('confirmed');
    // Actual: 'pending'
  3. Database constraint

    php
    User::create(['email' => 'duplicate@test.com']);
    // Unique constraint violation

Solutions

  1. Add null checks:

    php
    $fresh = $order->fresh();
    expect($fresh)->not->toBeNull();
    expect($fresh->status)->toBe('confirmed');
  2. Debug the actual value:

    php
    dump($order->status);  // See actual value
    expect($order->status)->toBe('confirmed');
  3. Ensure proper test isolation

Pattern Matching Error

When the pattern doesn't match the expected text format:

PatternMatchException: Step text doesn't match expected format:
  Step: "Given a user John exists"
  Pattern: "a user {name} exists"

Expected format: a user "quoted string" exists
  Example: Given a user "John" exists

The pattern expects a quoted string for {name} because
the parameter is typed as 'string'.

Causes

  1. Missing quotes for string

    gherkin
    Given a user John exists    # Should be "John"
  2. Quotes for integer

    gherkin
    Given there are "5" items   # Should be 5

Solutions

Follow the type conventions:

  • string → use quotes: "value"
  • int → bare number: 42
  • float → bare decimal: 3.14
  • bool → keywords: true, false, yes, no

Factory Error

When factory creation fails:

FactoryExecutionException: Failed to create model from factory
  Factory: Database\Factories\UserFactory
  Method: withName

Error: SQLSTATE[23000]: Integrity constraint violation:
  1062 Duplicate entry 'john@test.com' for key 'users.email_unique'

Suggested fix:
  - Use unique email: User::factory()->create(['email' => $this->faker->unique()->email()])
  - Or reset database between tests

Causes

  1. Database state pollution

    • Previous test left data behind
  2. Unique constraint violation

    • Faker generated duplicate value
  3. Missing migration

    • Table doesn't exist

Solutions

  1. Use database transactions in tests
  2. Use ->unique() with Faker
  3. Run migrations before tests

Debugging Tips

Enable Verbose Output

See step-by-step execution:

bash
pest --bdd -v

Add Debug Output

Temporarily add dumps:

php
#[Given('a user {name} exists')]
public function userExists(string $name): User
{
    dump("Creating user: $name");
    $user = User::factory()->create(['name' => $name]);
    dump("Created user ID: {$user->id}");
    return $user;
}

Check Context State

Inspect what's available:

php
#[When('I debug the context')]
public function debugContext(ScenarioContext $context): void
{
    dump($context->getTypes());
    dump($context->getAliases());
}

Isolate the Failure

Run single scenario:

bash
pest --bdd --filter="Scenario name"

Check Step Discovery

Verify steps are found:

bash
composer dump-autoload --optimize
# Then run tests

Error Reference

ErrorLikely CauseQuick Fix
Step not foundTypo or missing stepCheck spelling, add step
Parameter resolutionMissing Given stepAdd prerequisite step
Type mismatchWrong return typeReturn correct type
Pattern mismatchWrong value formatUse quotes/no quotes correctly
Factory errorDB constraintUse unique values
Null referenceObject not createdCheck Given steps

Best Practices

Write Defensive Steps

php
#[Then('the user should be active')]
public function userActive(User $user): void
{
    // Refresh from database
    $fresh = $user->fresh();

    // Verify it exists
    expect($fresh)->not->toBeNull()
        ->and($fresh->is_active)->toBeTrue();
}

Use Descriptive Assertions

php
// Better error messages
expect($order->status)
    ->toBe('confirmed', "Order {$order->id} should be confirmed");

Test Prerequisites

php
#[Given('a confirmed order exists')]
public function confirmedOrder(): Order
{
    $order = Order::factory()->confirmed()->create();

    // Verify setup worked
    expect($order->status)->toBe('confirmed');

    return $order;
}

Released under the MIT License.