Skip to content
Pest Flow
Esc
↑↓navigate↵open⌘Jpreview
On this page

Build your first scenario

Write and run a complete contractor activation scenario with Pest Flow.

This walkthrough builds a test for one business rule: only compliant contractors can activate. It uses ordinary Pest state and expectations, organized as a feature, rule, scenario, and three steps.

1. Import the DSL

In a Pest test file, import the functions you want to use:

use function Pest\Flow\{feature, rule, scenario, given, when, then};

The functions are loaded by Composer. You only need these imports to call them without a fully qualified name.

2. Name the behavior

Start with a feature and the rule it demonstrates:

feature('Contractor activation', function (): void {
    rule('Only compliant contractors may activate', function (): void {
        // Scenarios for this rule go here.
    });
});

Pest Flow maps feature() and rule() to nested Pest describe groups. Put Pest setup hooks in the group whose tests need them.

3. Add a scenario and state

A scenario is a regular Pest test. Use given() to establish its starting state on Pest’s test object:

scenario('Activates a compliant contractor', function (): void {
    given('a compliant contractor', function (): void {
        $this->contractor = [
            'compliant' => true,
            'active' => false,
        ];
    });
});

4. Add the action and expectation

Use when() for the action and then() for the assertion. The same $this->contractor is available in all three callbacks:

when('they activate', function (): void {
    if (! $this->contractor['compliant']) {
        throw new RuntimeException('Only compliant contractors may activate.');
    }

    $this->contractor['active'] = true;
});

then('activation is recorded', function (): void {
    expect($this->contractor['active'])->toBeTrue();
});

If the action throws, Pest marks the scenario as failed and the Then step does not run. For this successful example the contractor is compliant, so the Then expectation passes.

5. Run it

Save the complete test as tests/Feature/ContractorActivationTest.php, then run it from the project root:

vendor/bin/pest tests/Feature/ContractorActivationTest.php

The complete file below also inspects the behaviour registry and asserts the generated IDs. The imports and assertions are ordinary Pest and PHP code; Pest Flow only provides the behaviour grouping and step recording.

Complete test

<?php

declare(strict_types=1);

use Pest\Flow\FlowRegistry;
use Pest\Flow\Model\StepNode;

use function Pest\Flow\feature;
use function Pest\Flow\given;
use function Pest\Flow\rule;
use function Pest\Flow\scenario;
use function Pest\Flow\then;
use function Pest\Flow\when;

feature('Contractor activation', function (): void {
    rule('Only compliant contractors may activate', function (): void {
        scenario('Activates a compliant contractor', function (): void {
            given('a compliant contractor', function (): void {
                $this->contractor = [
                    'compliant' => true,
                    'active' => false,
                ];
            });

            when('they activate', function (): void {
                if (! $this->contractor['compliant']) {
                    throw new RuntimeException('Only compliant contractors may activate.');
                }

                $this->contractor['active'] = true;
            });

            then('activation is recorded', function (): void {
                expect($this->contractor['active'])->toBeTrue();

                $feature = FlowRegistry::features()[0];
                $rule = $feature->rules()[0];
                $scenario = $rule->scenarios()[0];

                expect($feature->id)->toBe('contractor-activation')
                    ->and($rule->id)->toBe('contractor-activation/only-compliant-contractors-may-activate')
                    ->and($scenario->id)->toBe('contractor-activation/only-compliant-contractors-may-activate/activates-a-compliant-contractor')
                    ->and(array_map(
                        static fn (StepNode $step): string => $step->id,
                        $scenario->steps(),
                    ))->toBe([
                        'contractor-activation/only-compliant-contractors-may-activate/activates-a-compliant-contractor/given-a-compliant-contractor',
                        'contractor-activation/only-compliant-contractors-may-activate/activates-a-compliant-contractor/when-they-activate',
                        'contractor-activation/only-compliant-contractors-may-activate/activates-a-compliant-contractor/then-activation-is-recorded',
                    ]);
            });
        });
    });
});

What just happened

  1. Pest loaded the feature, rule, and scenario declarations and registered those nodes.
  2. Pest ran the scenario as a test and called Given, When, then Then on the same test object.
  3. Each step was recorded as it ran. The Then callback could inspect the scenario’s steps and IDs.

Continue with Inspect the registry to see the node fields and when each part of the registry is available.

Was this page helpful?