Features & Steps
phpspec 9 unifies Story BDD and Spec BDD. Write acceptance scenarios in
Gherkin .feature files and implement them with step definitions —
no Behat required.
Feature files
Create .feature files in a features/ directory using standard Gherkin syntax:
Feature: Greeting As a user I want to be greeted So that I feel welcome Scenario: Say hello Given a greeting service When I greet "World" Then I should see "Hello, World!"
Step definitions
Define steps in features/steps/*.steps.php:
<?php given('a greeting service', function () { $this->greeter = new App\Greeter(); }); when('I greet {string}', function (string $name) { $this->result = $this->greeter->greet($name); }); then('I should see {string}', function (string $expected) { expect($this->result)->toBe($expected); });
DSL functions
| Function | Purpose |
|---|---|
given(pattern, closure) | Register a Given step |
when(pattern, closure) | Register a When step |
then(pattern, closure) | Register a Then step |
step_and(pattern, closure) | Register an And step |
step_but(pattern, closure) | Register a But step |
All keywords register into the same step registry — the keyword is for readability only.
Step patterns
Patterns use placeholders that capture values from step text:
| Placeholder | Matches | PHP type |
|---|---|---|
{string} | Quoted text "..." | string |
{int} | Integer \d+ | int |
{word} | Single word \w+ | string |
{*} | Anything .+ | string |
given('there are {int} cucumbers', function (int $count) { $this->count = $count; }); when('I eat {int} cucumbers', function (int $eaten) { $this->count -= $eaten; });
Shared world
Each scenario gets a fresh StepWorld object. Steps within a scenario
share state via $this:
given('I store {string} as message', function (string $msg) { $this->message = $msg; }); then('the message should be {string}', function (string $expected) { expect($this->message)->toBe($expected); });
Background
Background steps run before each scenario in a feature:
Feature: Background example Background: Given a common setup Scenario: First Then the setup should be available Scenario: Second Then the setup should be available
Scenario Outline
Expand a scenario template with multiple data rows:
Scenario Outline: Eating Given there are <start> cucumbers When I eat <eat> cucumbers Then I should have <left> cucumbers Examples: | start | eat | left | | 12 | 5 | 7 | | 20 | 5 | 15 |
Data tables
Pass tabular data to a step:
Scenario: User list Given the following users: | name | role | | Alice | admin | | Bob | user | Then there should be 2 users
use PhpSpec\StoryBDD\DataTable; given('the following users:', function (DataTable $table) { $this->users = $table->toArray(); // [['name' => 'Alice', 'role' => 'admin'], ...] }); then('there should be {int} users', function (int $count) { expect($this->users)->toHaveCount($count); });
DataTable implements ArrayAccess, Iterator, and Countable. Use $table->asClass(User::class) to hydrate rows into objects.
Tags
Add @tag annotations to features and scenarios:
@smoke Feature: Tagged feature @wip Scenario: Tagged scenario Given a step
Step states
| State | Meaning |
|---|---|
| passed | Step executed successfully |
| failed | Step threw an exception or expectation failed |
| pending | Step calls pending() |
| undefined | No matching step definition found |
| skipped | Skipped because a prior step failed |
Running features
$ bin/phpspec run features/ # all features $ bin/phpspec run features/greeting.feature # single feature $ bin/phpspec run --story # only features, skip specs $ bin/phpspec run --all # specs + features
Step generation
When running features with undefined steps, phpspec offers to generate step definition stubs with pending() calls:
3 undefined steps in features/greeting.feature. Generate step definitions? [Y/n] y Generated step stubs in features/steps/greeting.steps.php
The BDD cycle
Story BDD and Spec BDD form a complete development cycle:
Feature (acceptance) ↓ Steps (implement step definitions) ↓ Specs (unit specs for each class) ↓ Classes (implement source code) ↓ Green (everything passes)
- Write a
.featurefile describing desired behaviour - Run
bin/phpspec run features/— steps are undefined - Generate step definitions, implement them
- Steps reference classes that don’t exist — phpspec generates specs and classes
- Switch to Spec BDD: write specs, generate classes, implement
- Return to features — steps now pass