PHPackages                             jgss/laravel-pest-scenarios - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. jgss/laravel-pest-scenarios

ActiveLibrary[Testing &amp; Quality](/categories/testing)

jgss/laravel-pest-scenarios
===========================

Declarative, consistent and reusable test scenarios for Laravel + Pest

1.0.3(4mo ago)072MITPHPPHP ^8.3CI passing

Since Dec 16Pushed 4mo agoCompare

[ Source](https://github.com/julien-gassmann/laravel-pest-scenarios)[ Packagist](https://packagist.org/packages/jgss/laravel-pest-scenarios)[ RSS](/packages/jgss-laravel-pest-scenarios/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (11)Versions (5)Used By (0)

Laravel Pest Scenarios
======================

[](#laravel-pest-scenarios)

> Declarative, consistent and reusable test scenarios for Laravel + Pest.

[![Tests](https://camo.githubusercontent.com/5b83d1f21f92cbbe69f885b4b248bfae0907098dcbf53e95dd8dcd14e9fcf723/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6a756c69656e2d676173736d616e6e2f6c61726176656c2d706573742d7363656e6172696f732f74657374732e796d6c3f6c6162656c3d5465737473)](https://github.com/julien-gassmann/laravel-pest-scenarios/actions/workflows/tests.yml)[![Coverage](https://camo.githubusercontent.com/a47a770765f8c7b5776c024ff602bfc3f3686ba8be5317b3d0c320c715df42c1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d3130302532352d73756363657373)](https://camo.githubusercontent.com/a47a770765f8c7b5776c024ff602bfc3f3686ba8be5317b3d0c320c715df42c1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d3130302532352d73756363657373)[![Latest Version on Packagist](https://camo.githubusercontent.com/6a068c0fcdd2963f154d7279886d72b4855732d420cf523b80009a6491472d38/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6773732f6c61726176656c2d706573742d7363656e6172696f732e737667)](https://packagist.org/packages/jgss/laravel-pest-scenarios)[![License: MIT](https://camo.githubusercontent.com/1a2e0606685ce00663bf829868f794fd3fc9c86f8d80cae324734129e0723a58/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d627269676874677265656e2e737667)](https://opensource.org/licenses/MIT)

---

Introducing laravel-pest-scenarios
----------------------------------

[](#introducing-laravel-pest-scenarios)

A lightweight layer on top of Pest that makes your Laravel tests **clear**, **declarative**, and **uniform** across your entire codebase.

Instead of rewriting setup logic and repeating the same assertions in every test, this package lets you define **Contexts** (shared setup) and **Scenarios** (declarative test cases) that are reusable across your test suite.
This way, you can focus on **what** should happen in your tests rather than **how** to implement them.

It comes with several prebuilt scenario types for both **feature** and **unit** tests (with [more to come](#contributing--roadmap)):

- **Feature tests**:
    - [API routes](docs/feature/api-routes.md) → full HTTP endpoint
    - [Web routes](docs/feature/web-routes.md) → full browser-oriented route
- **Unit tests**:
    - [Commands](docs/unit/commands.md) → Artisan scenario testing
    - [FormRequests](docs/unit/form-requests) → authorization + validation
    - [Models](docs/unit/model.md) → methods, scopes, traits
    - [Policies](docs/unit/policies.md) → authorization only
    - [Rules](docs/unit/rules.md) → validator logic only

You also get a set of globally available [helpers](#helpers) (actors, database setups, queries, mocks, JSON structures), making your tests even cleaner and more consistent.

Note

This package covers roughly 80–90% of common Laravel test scenarios. For complex multistep logic (e.g., updating a password with multiple dependent checks), standard Pest tests may still be necessary.

---

Why Use It?
-----------

[](#why-use-it)

**For Test Beginners**

- **Guided structure**: Clear test templates with an opinionated layout.
- **Blank-file syndrome**: Predefined scenario types, so you never start from nothing.
- **Fill-in-the-blanks approach**: Tests are already written, you only provide the missing pieces.
- **Progressive learning**: Start simple, then expand with database checks, mocks, etc.

**For All Teams**

- **Less boilerplate**: Focus on testing behavior, not setup.
- **Consistency**: Uniform style across tests and developers.
- **Reusability**: Define contexts and helpers once, reuse anywhere.
- **Readability**: Tests are concise and descriptive.

---

Installation
------------

[](#installation)

This package requires :

- Laravel 11 &gt; 12
- Pest 3: PHP 8.3 &gt; 8.4
- Pest 4: PHP 8.3 &gt; 8.5
- Named routes (for most scenarios)

```
composer require --dev jgss/laravel-pest-scenarios
```

---

Configuration
-------------

[](#configuration)

This package is fully configurable via a dedicated config file.

```
php artisan vendor:publish --tag=pest-scenarios
```

The configuration allows you to:

- Control how strict the test suite should be (fail vs skip)
- Define reusable resolvers (actors, database setups, queries, JSON structures)

Detailed explanations are provided directly inside the published configuration file: [`config/pest-scenarios.php`](config/pest-scenarios.php).

---

Core Concepts
-------------

[](#core-concepts)

### Contexts

[](#contexts)

A Context stores all shared data for your scenarios: route infos, authenticated users, database setup, mocks, etc.
They are **immutable**. Modifier methods prefixed with `with` let you tweak a context safely for a specific scenario.

```
use App\Models\User;
use Illuminate\Notifications\Notification;
use Jgss\LaravelPestScenarios\Context;
use Mockery\MockInterface;
use function Jgss\LaravelPestScenarios\getActorId;
use function Jgss\LaravelPestScenarios\makeMock;

// Define your context once at the top of your test file
$context = Context::forApiRoute()->with(
    // --- Route infos -------------------------------------------------------------------------
    routeName: 'users.update',
    routeParameters: ['user' => getActorId('user')],
    // --- Authenticated user ------------------------------------------------------------------
    actingAs: 'admin',
    // --- Database setup ----------------------------------------------------------------------
    databaseSetup: ['create_user', 'create_admin'],
    // --- Mocked classes ----------------------------------------------------------------------
    mocks: makeMock(Notification::class, fn (MockInterface $mock) => $mock->shouldReceive('send')->once()),
);
```

### Scenarios

[](#scenarios)

A Scenario defines a single declarative test case built on top of a context. You can define valid and invalid variants to separate success and failure cases clearly.

To help you understand what this package brings on top of Pest, here’s a small comparison between a typical Pest test and the same test expressed through scenarios.

#### 🟢 Valid Scenarios:

[](#-valid-scenarios)

```
use App\Http\Resources\UserResource;
use Jgss\LaravelPestScenarios\Scenario;
use Illuminate\Notifications\Notification;
use Mockery\MockInterface;
use function Pest\Laravel\assertDatabaseHas;

// Using native Pest
it("returns 200 when admin updates user's profile", function () {
     // Arrange: Create user and admin
    $user = User::factory()->create(['role' => 'user']);
    $admin = User::factory()->create(['role' => 'admin']);

    // Arrange: Mock notifications
    mock(Notification::class, function (MockInterface $mock) {
        $mock->shouldReceive('send')->once();
    });

    // Act: Send request with payload
    $payload = ['name' => 'New Name', 'email' => 'new@mail.com'];
    $response = actingAs($admin)
        ->patchJson("/users/{$user->id}", $payload);

    // Assert: Check status code and JSON structure
    $response->assertStatus(200)
        ->assertJsonStructure(['data']);

    // Assert: Check if response contains the expected resource
    $expectedResponse = UserResource::make($user->refresh())->response();
    expect($response->json())->toEqual($expectedResponse);

    // Assert: Check new row insertion in database
    assertDatabaseHas('users', [
        'id' => $user->id,
        'name' => 'New Name',
        'email' => 'new@mail.com',
        'updated_by' => $admin->id,
    ]);
});

// Using laravel-pest-scenarios
Scenario::forApiRoute()->valid(
    description: "returns 200 when admin updates user's profile",
    // --- Context ----------------------------------------------------------------------
    context: $context,
    // --- Payload ----------------------------------------------------------------------
    payload: ['name' => 'New Name', 'email' => 'new@mail.com'],
    // --- Expected response ------------------------------------------------------------
    expectedResponse: fn () => UserResource::make(actor('user'))->response(),
    // --- Database assertions ----------------------------------------------------------
    databaseAssertions: [
        fn () => assertDatabaseHas('users', [
            'id' => actorId('user'),
            'name' => 'New Name',
            'email' => 'new@mail.com',
            'updated_by' => actorId('admin'),
        ]),
    ]
);
```

#### 🔴 Invalid Scenarios:

[](#-invalid-scenarios)

```
use App\Models\User;
use Jgss\LaravelPestScenarios\Scenario;

// Using native Pest
it("returns 404 when updating non-existent id", function () {
    // Arrange: Create admin
    $admin = User::factory()->create();

    // Act: Send request with payload
    $payload = ['name' => 'New Name', 'email' => 'new@mail.com'];
    $response = actingAs($admin)
        ->patchJson('/users/999999', $payload);

    // Assert: Check status code and JSON content
    $response
        ->assertStatus(404)
        ->assertJson(['message' => "User '999999' not found."]);
});

// Using laravel-pest-scenarios
Scenario::forApiRoute()->invalid(
    description: 'returns 404 when updating non-existent id',
    // --- Context --------------------------------------------------------------------
    context: $context->withRouteParameters(['user' => '999999']),
    // --- Status code ----------------------------------------------------------------
    expectedStatusCode: 404,
    // --- Error message --------------------------------------------------------------
    expectedErrorMessage: "User '999999' not found.",
);
```

Note

The `->valid()` and `->invalid()` methods generate Pest test definitions using `it()`, so you can chain modifiers like `->skip()` or `->only()` for flexible test control.
They can also be wrapped inside `describe()` blocks to organize tests hierarchically.

---

Helpers
-------

[](#helpers)

To make your tests faster, cleaner, and more maintainable, this package provides a set of **reusable helpers** based on **configurable keys** defined in your [config](config/pest-scenarios.php).

Think of them as ready-made building blocks: instead of repeating setup code, database queries, or mocks, you can just reference a helper. This keeps your tests **concise**, **readable**, and **easy to maintain**, even in large projects.

Here’s what you get out of the box:

- [Actors](docs/helpers/actors.md) → perfect for authentication and user-specific scenarios.
- [Database Setups](docs/helpers/database-setups.md) → populate tables without repeating factories everywhere.
- [Queries](docs/helpers/queries.md) – centralize frequent queries for consistent and reusable data access.
- [JSON Structures](docs/helpers/json-structures.md) → easily validate API responses without verbose arrays.
- [Mock Factory](docs/helpers/mocks.md) – simplify mock definitions by generating the exact structure required by Contexts.

Tip

These helpers are particularly powerful when combined with **Contexts** and **Scenarios**, letting you define once and reuse everywhere. But they are still available in native Pest tests when needed.

---

Quick Start (5 minutes)
-----------------------

[](#quick-start-5-minutes)

Get started with a working test in just a few minutes:

### Step 1: Configure your actors and database

[](#step-1-configure-your-actors-and-database)

```
// config/pest-scenarios.php
'actors' => [
    'admin' => fn () => User::where('role', 'admin')->firstOrFail(),
],
'database_setups' => [
    'create_admin' => fn () => User::factory()->create(['role' => 'admin']),
],
```

### Step 2: Generate a test file

[](#step-2-generate-a-test-file)

```
php artisan make:scenario ApiRoute Feature/Api/UserIndexTest
› Q: "Which route do you want to test?"
› A: "users.index"
```

### Step 3: Create your first Scenario

[](#step-3-create-your-first-scenario)

```
// tests/Feature/Api/UserIndexTest.php -> valid scenarios section
Scenario::forApiRoute()->valid(
    description: 'returns 200',
    context: $context
        ->withDatabaseSetup('create_admin') // Fill your database when not using seeders
        ->withActingAs('admin') // Set actingAs() user if route needs authentication
)
```

This is a minimal scenario asserting a status 200 (default value for API route valid scenario's `expectedStatusCode` property).
You can add more valid or invalid scenarios to fully cover the route.

### Step 4: Run your tests

[](#step-4-run-your-tests)

```
php artisan test
```

That's it! You now have a structured test file ready to customize.

---

Contributing &amp; Roadmap
--------------------------

[](#contributing--roadmap)

Contributions are welcome! Run the test suite before pushing:

```
composer check
```

Future improvements:

- Multistep scenario support
- Dataset utilities for scenarios
- MCP server
- Custom scenarios
- Browser-testing compatibility
- Scenarios for: Actions, Middlewares, Events, Jobs and Notifications

---

Support / Contact
-----------------

[](#support--contact)

Maintained by [J.G.](https://github.com/julien-gassmann)If you find this package useful, feel free to star ⭐ the repo or share feedback!

###  Health Score

39

—

LowBetter than 86% of packages

Maintenance74

Regular maintenance activity

Popularity12

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

147d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/facd5cd3b107a82663805328f43caa883ad3cefe4764037d05fe10127aa0dbf0?d=identicon)[julien-gassmann](/maintainers/julien-gassmann)

---

Top Contributors

[![julien-gassmann](https://avatars.githubusercontent.com/u/80699369?v=4)](https://github.com/julien-gassmann "julien-gassmann (61 commits)")

---

Tags

phptestingpestlaravelunit testingapi testingform-requestfeature-testsscenariostest-scenarios

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jgss-laravel-pest-scenarios/health.svg)

```
[![Health](https://phpackages.com/badges/jgss-laravel-pest-scenarios/health.svg)](https://phpackages.com/packages/jgss-laravel-pest-scenarios)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[nunomaduro/laravel-mojito

A lightweight package for testing Laravel views.

368435.5k11](/packages/nunomaduro-laravel-mojito)[defstudio/pest-plugin-laravel-expectations

A plugin to add laravel tailored expectations to Pest

98548.9k4](/packages/defstudio-pest-plugin-laravel-expectations)[code-distortion/adapt

A Laravel package that builds databases for your tests, improving their speed.

2835.5k](/packages/code-distortion-adapt)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
