Skip to content

Multiple Instances

When a scenario needs multiple instances of the same model, use aliases to distinguish them. This allows you to create and reference multiple users, orders, or any other model independently.

Alias Syntax

Add as {aliasName} to the end of a Given step:

gherkin
Scenario: Two users comparison
  Given a user "John" exists as {firstUser}
  Given a user "Jane" exists as {secondUser}
  When I compare the users
  Then firstUser should not equal secondUser

The alias name in curly braces becomes the parameter name for injection.

How Aliases Work

Without Alias (Single Instance)

gherkin
Given a user "John" exists
Given a user "Jane" exists
# Only "Jane" is available - overwrote "John"

When you don't use aliases, later steps overwrite earlier ones of the same type.

With Alias (Multiple Instances)

gherkin
Given a user "John" exists as {john}
Given a user "Jane" exists as {jane}
# Both are available: john and jane

With aliases, each instance is stored separately and can be accessed by its alias name.

Injection by Alias

Access aliased models by matching parameter names:

php
#[Then('firstUser should not equal secondUser')]
public function usersNotEqual(User $firstUser, User $secondUser): void
{
    expect($firstUser->id)->not->toBe($secondUser->id);
}

#[When('I transfer from john to jane')]
public function transfer(User $john, User $jane): void
{
    $john->transferCreditsTo($jane, 100);
}

The parameter name must match the alias exactly.

Complete Example

Feature File

gherkin
Feature: Money Transfer
  As a user
  I want to transfer money to another user
  So that I can pay them for services

  Scenario: Successful transfer
    Given a user "Alice" exists as {sender}
    And a user with 1000 credits exists as {sender}
    Given a user "Bob" exists as {receiver}
    And a user with 0 credits exists as {receiver}
    When I transfer 500 credits from sender to receiver
    Then sender should have 500 credits
    And receiver should have 500 credits

  Scenario: Insufficient funds
    Given a user "Alice" exists as {sender}
    And a user with 100 credits exists as {sender}
    Given a user "Bob" exists as {receiver}
    When I try to transfer 500 credits from sender to receiver
    Then the transfer should fail with "Insufficient funds"
    And sender should have 100 credits
    And receiver should have 0 credits

Step Definitions

php
class TransferSteps
{
    private ?TransferResult $result = null;

    #[When('I transfer {amount} credits from sender to receiver')]
    public function transfer(int $amount, User $sender, User $receiver): void
    {
        $this->result = TransferService::transfer($sender, $receiver, $amount);
    }

    #[When('I try to transfer {amount} credits from sender to receiver')]
    public function tryTransfer(int $amount, User $sender, User $receiver): void
    {
        try {
            $this->result = TransferService::transfer($sender, $receiver, $amount);
        } catch (InsufficientFundsException $e) {
            $this->result = TransferResult::failed($e->getMessage());
        }
    }

    #[Then('sender should have {credits} credits')]
    public function senderCredits(int $credits, User $sender): void
    {
        expect($sender->fresh()->credits)->toBe($credits);
    }

    #[Then('receiver should have {credits} credits')]
    public function receiverCredits(int $credits, User $receiver): void
    {
        expect($receiver->fresh()->credits)->toBe($credits);
    }

    #[Then('the transfer should fail with {message}')]
    public function transferFailed(string $message): void
    {
        expect($this->result->failed)->toBeTrue();
        expect($this->result->message)->toBe($message);
    }
}

Chaining States with Aliases

Aliases work with state chaining. All Given steps with the same alias contribute to the same model:

gherkin
Scenario: Complex user setup with alias
  Given a user "Admin Alice" exists as {admin}
  And an admin user exists as {admin}
  And a verified user exists as {admin}

  Given a user "Regular Bob" exists as {regular}
  And a user with 50 credits exists as {regular}

  When admin reviews regular's account
  Then admin should see Bob with 50 credits

Both chains build up independently:

  • {admin}: User named "Admin Alice" + admin role + verified
  • {regular}: User named "Regular Bob" + 50 credits

Common Patterns

Comparison Testing

gherkin
Scenario: Compare premium vs regular user access
  Given a user "Premium User" exists as {premium}
  And a premium user exists as {premium}

  Given a user "Regular User" exists as {regular}

  When both users try to access premium features
  Then premium should have access
  And regular should not have access

Relationship Testing

gherkin
Scenario: Follow relationship
  Given a user "Alice" exists as {follower}
  Given a user "Bob" exists as {followed}

  When follower follows followed
  Then followed should have 1 follower
  And follower should be following 1 user

Parent-Child Relationships

gherkin
Scenario: Organization hierarchy
  Given a user "CEO" exists as {ceo}
  And an admin user exists as {ceo}

  Given a user "Manager" exists as {manager}
  And a manager user exists as {manager}

  Given a user "Employee" exists as {employee}

  When I build the org chart
  Then ceo should be at the top
  And manager should report to ceo
  And employee should report to manager

Transaction Testing

gherkin
Scenario: Order between buyer and seller
  Given a user "Buyer" exists as {buyer}
  And a user with 1000 credits exists as {buyer}

  Given a user "Seller" exists as {seller}
  And a merchant user exists as {seller}

  Given a product "Widget" exists as {product}
  And a product with price 100 exists as {product}

  When buyer purchases product from seller
  Then buyer should have 900 credits
  And seller should have received 100 credits
  And product should be marked as sold

Multiple Instances of Different Types

You can alias different model types:

gherkin
Scenario: Complex e-commerce flow
  Given a user "John" exists as {customer}
  Given a user "Admin" exists as {admin}
  And an admin user exists as {admin}

  Given a product "Widget" exists as {mainProduct}
  And a product with price 50 exists as {mainProduct}

  Given a product "Accessory" exists as {accessory}
  And a product with price 10 exists as {accessory}

  When customer adds mainProduct to cart
  And customer adds accessory to cart
  Then the cart total should be 60

Without Factory Integration

You can also use aliases with regular step definitions:

php
class UserSteps
{
    #[Given('a user {name} exists')]
    public function userExists(string $name): User
    {
        return User::factory()->create(['name' => $name]);
    }
}

The alias is added in the feature file, not the step definition:

gherkin
Given a user "John" exists as {john}
# The returned User is stored with alias "john"

Best Practices

Use Descriptive Alias Names

gherkin
# Good: Clear intent
Given a user "John" exists as {buyer}
Given a user "Jane" exists as {seller}

# Avoid: Generic names
Given a user "John" exists as {user1}
Given a user "Jane" exists as {user2}

Match Alias to Role

gherkin
# Good: Alias reflects the user's role in the scenario
Given a user "Alice" exists as {reviewer}
Given a user "Bob" exists as {author}
When reviewer reviews author's submission

# The step definition is clear:
#[When('reviewer reviews author\'s submission')]
public function review(User $reviewer, User $author): void

Keep Alias Count Manageable

If you need many aliased instances, consider if the scenario is too complex:

gherkin
# Consider splitting if you have many aliases
Given a user exists as {user1}
Given a user exists as {user2}
Given a user exists as {user3}
Given a user exists as {user4}
Given a user exists as {user5}
# Maybe this should be multiple scenarios?

Document Complex Scenarios

gherkin
# Multi-party transaction with clear aliases
Scenario: Three-way trade
  # Alice sells to Bob, Bob sells to Charlie
  Given a user "Alice" exists as {alice}
  Given a user "Bob" exists as {bob}
  Given a user "Charlie" exists as {charlie}
  # ... clear who does what

Released under the MIT License.