Search by

eubentof / gherkish

eubentof

Keep Gherkin scenarios and Pest tests in sync in Laravel applications.

Package info

github.com/eubentof/gherkish

pkg:composer/eubentof/gherkish

Statistics

Installs: 131

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.6.4 2026-09-08 17:29 UTC

This package is auto-updated.

Last update: 2026-09-08 18:42:35 UTC


README

Gherkish checks that each scenario in your Laravel application's .feature files has a matching Pest test and matching @Given, @When, @Then, @And, and @But docblocks.

Installation

Install the package as a development dependency:

composer require --dev eubentof/gherkish

Laravel discovers the package automatically. Run the checker with:

php artisan gherkish:check

By default, Gherkish recursively discovers .feature files below app/ and tests/. A feature is paired with a test of the same basename in the same directory:

CreateUser.feature
CreateUserTest.php

A feature can instead declare one or more test files:

Feature: Create a user

  @tests:
  - tests/Feature/Users/CreateUserTest.php

  Scenario: should create a user
    Given valid user details
    When the user is created
    Then the user is persisted

The Pest description and step docblocks must match:

it('should create a user', function () {
    /** @Given valid user details */
    $details = ['name' => 'Ada'];

    /** @When the user is created */
    $user = User::create($details);

    /** @Then the user is persisted */
    expect($user->exists)->toBeTrue();
});

Each step docblock must have executable PHP code on the line directly below it. Another step docblock, a regular comment, or a blank line is not considered an implementation and causes gherkish:check to fail.

Scenario Outline datasets

Gherkish::examples() reads the Examples table from the Scenario Outline paired with the current Pest test. Its rows are returned as associative arrays, ready for Pest's with() dataset method:

Scenario Outline: User logs in
  Given a user with email "<email>"
  When they log in with password "<password>"
  Then the result should be "<result>"

  Examples:
    | email            | password | result  |
    | john@test.com    | correct  | success |
    | missing@test.com | anything | failure |
use Gherkish\Gherkish;

it('User logs in', function (string $email, string $password, string $result) {
    // ...
})->with(Gherkish::examples());

When an outline has multiple Examples blocks, give each one a label and pass one or more desired labels. Rows are combined in the same order as the labels:

Examples: Valid credentials
  | email         | password | result  |
  | john@test.com | correct  | success |

Examples: Invalid credentials
  | email         | password | result  |
  | john@test.com | wrong    | failure |
it('User logs in', function (string $email, string $password, string $result) {
    // ...
})->with(Gherkish::examples('Valid credentials', 'Invalid credentials'));

The feature must use the normal same-directory pairing convention, such as Login.feature and LoginTest.php. A label is optional when the current outline contains exactly one Examples block and required when it contains more than one.

With --outlined (or -o), gherkish:check also verifies that every Scenario Outline maps its Examples rows to the matching Pest test. Use Gherkish::examples(), combine labeled blocks with Gherkish::examples('First label', 'Second label'), or provide a static literal dataset whose values exactly match the Examples table:

test('User logs in', function (string $email, string $password, string $result) {
    // ...
})->with([
    ['john@test.com', 'correct', 'success'],
    ['missing@test.com', 'anything', 'failure'],
]);

Gherkish does not execute dynamic dataset providers while checking parity. Datasets that cannot be verified statically should use Gherkish::examples(). If a custom provider is required, place the scoped ignore comment directly above the Pest test:

// @gherkish-ignore-examples
test('User logs in', function (string $email, string $password, string $result) {
    // Scenario and step parity are still checked.
})->with(customLoginDataset());

This skips only Examples-to-dataset validation. The checker still validates the scenario description, step docblocks, and executable code below each step.

Background and beforeEach mapping

Background parity is part of the default check and is independent from --phased. When a feature declares a Background, every matching scenario test must have an applicable Pest beforeEach() whose step docblocks map all Background steps. As with scenario steps, each mapped docblock must have executable PHP directly below it.

Background:
  Given an authenticated administrator

Scenario: User opens the dashboard
  When the dashboard is requested
  Then the dashboard is displayed
beforeEach(function () {
    /** @Given an authenticated administrator */
    $this->actingAs(User::factory()->admin()->create());
});

test('User opens the dashboard', function () {
    /** @When the dashboard is requested */
    $response = $this->get('/dashboard');

    /** @Then the dashboard is displayed */
    $response->assertOk();
});

Applicable beforeEach() step annotations also participate in normal scenario mapping when the feature has no Background. This allows a Given declared in a Scenario to be implemented once in shared Pest setup.

Scope follows Pest describe() nesting: a top-level beforeEach() applies to every test in the file, while a setup inside describe() applies only to tests inside that describe block and its nested descendants. It does not apply to a sibling describe block.

Snapshots expose Background coverage once in the feature-level background entry and list parsed setup annotations under beforeEach. Background steps are not duplicated in each scenario's step cases. Descriptive command output renders one BACKGROUND group per feature, and summary case totals count each Background step once.

This changes the previous Background behavior: existing feature files whose Background steps were ignored must move their matching annotations and implementations into an applicable beforeEach().

Reverse test mapping

Use --unmapped (or -u) to also verify parity in the other direction: every Pest test() or it() block in the selected scope must map to a Scenario in its paired feature file or in a feature file that lists the test under @tests:.

If a Pest test intentionally has no feature scenario, place the scoped ignore comment directly above it:

// @gherkish-ignore-mapping
test('covers an internal implementation detail', function () {
    // This test is excluded only from reverse mapping validation.
});

Feature-to-test scenario and step checks still run normally. The ignore comment only skips the opt-in reverse mapping check for that Pest test.

Phased scenario structure

Use --phased or -p to require every Scenario and Scenario Outline to contain effective Given, When, and Then phases. And and But inherit the phase established by the preceding primary keyword, but cannot establish a phase when they appear before any Given, When, or Then step.

Phased structure validation is opt-in and independent from --outlined and --unmapped. It validates only the steps declared inside each scenario; Background steps are not included in this structure check.

php artisan gherkish:check --phased

Command options

By default, the checker prints one compact status character per scenario: a green . for covered, a red F for failed, or a yellow S for skipped. Failure details are collected after the progress dots. Use --long or -l to show the full scenario, step, and Examples output. The summary includes the total inner step cases checked across all scenarios and the command duration.

Pass these options to php artisan gherkish:check:

Option Description Environment variable
--dir=tests/Feature Limit the check to feature files and, for reverse validation, Pest tests inside a directory. GHERKISH_DIR
--feature=tests/Feature/Users/CreateUser.feature Check a specific feature file. GHERKISH_FILE or GHERKISH_FEATURE
--file=tests/Feature/Users/CreateUser.feature Alias for --feature. GHERKISH_FILE or GHERKISH_FEATURE
--f=tests/Feature/Users/CreateUser.feature Short alias for --feature. GHERKISH_FILE or GHERKISH_FEATURE
--staged, -s Check only staged feature files and features paired with staged Pest tests. Cannot be combined with a path filter.
--outlined, -o Validate Scenario Outline datasets against their Examples tables. GHERKISH_CHECK_OUTLINE_DATASETS=1
--unmapped, -u Validate that every Pest test maps to a Gherkin scenario. GHERKISH_CHECK_UNMAPPED_TESTS=1
--phased, -p Require every Scenario and Scenario Outline to contain effective Given, When, and Then phases. GHERKISH_STRICT=1
--long, -l Show every scenario, step, and Examples table instead of compact status dots.
--snapshot=storage/app/feature-parity.json Write the coverage snapshot as JSON. GHERKISH_SNAPSHOT

Configuration variables use the GHERKISH_ prefix. Replace any previous FEATURE_PARITY_* variables in local environments and CI configuration.

Use --staged or -s in pre-commit workflows to select added, copied, modified, and renamed files from the Git index:

php artisan gherkish:check --staged

Staged .feature files are checked against their mapped Pest files. Staged PHP test files select same-basename features and features that list them under @tests:. Deleted files, feature files outside the application's app/ and tests/ roots, and unlinked tests outside those roots are ignored. The checker validates the current working tree contents of each selected file and succeeds with a skipped selection when no relevant files are staged.

Development

composer install
composer test
composer format