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
- Pest loaded the feature, rule, and scenario declarations and registered those nodes.
- Pest ran the scenario as a test and called
Given,When, thenThenon the same test object. - Each step was recorded as it ran. The
Thencallback 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.