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

    • Verify class is in a PSR-4 autoloaded directory
    • Check namespace matches composer.json autoload configuration
  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 a PSR-4 autoloaded directory
  4. Verify namespace matches your composer.json autoload configuration

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 ​

If steps aren't found, verify:

  • Class is in a PSR-4 autoloaded directory
  • Namespace matches composer.json autoload configuration
  • File has no syntax errors

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.