defstudio/pest-plugin-laravel-expectations
A Pest plugin that adds expressive Laravel-specific expectations to your test suite, making assertions for models, HTTP responses, authentication, and common framework behavior cleaner and more readable. Write concise, fluent tests without verbose Laravel checks.
defstudio/pest-plugin-laravel-expectations is a Pest plugin that adds Laravel-aware expectations so common framework assertions read like natural language instead of $this->assertTrue(...) calls. It is a test-only dependency.
Install
composer require defstudio/pest-plugin-laravel-expectations --dev
Pest auto-discovers the plugin through Composer, so there is usually nothing to register. If the new expectations are not available in your suite:
composer dump-autoloadrequire-dev and installed (composer show defstudio/pest-plugin-laravel-expectations)tests/Pest.php — if your suite already calls uses(...) for other plugins, make sure nothing is overriding plugin loading.Where to look first
vendor/defstudio/pest-plugin-laravel-expectations/src/Expectations/ — the actual expectation classes. This is the fastest way to see exactly what is supported in the version you installed (more reliable than blog posts or older docs).vendor/defstudio/pest-plugin-laravel-expectations/composer.json — check the pestphp/pest constraint before upgrading, especially around a Pest major release.First use case
Replace the assertion you write most often. A typical first win is auth and model-state assertions inside feature tests:
it('logs the user in', function () {
$this->post('/login', ['email' => '[email protected]', 'password' => 'secret']);
expect(auth()->user())->toBeAuthenticated();
});
it('soft deletes the post', function () {
$post->delete();
expect($post)->toBeTrashed();
});
Start by grepping your suite for assertAuthenticated, assertGuest, assertTrue($model->trashed()), and relationship assertions — those are the cheapest conversions and they make failures far easier to read.
Use it as a readability layer, not a replacement for Laravel's testing API
The plugin composes with standard Laravel testing helpers. Keep $this->actingAs(), $this->assertDatabaseHas(), and HTTP assertions as-is; use expectations where they remove boolean noise:
$this->actingAs($user);
expect($user)->toBeAuthenticated();
$this->get('/dashboard')->assertOk();
Auth assertions in middleware/policy tests
toBeAuthenticated() / toBeGuest() pair well with route-level auth tests, where the intent of the test is "is the guard state correct" rather than "did the response render."
Model-state and relationship assertions as regression guards
When you refactor a model (changing SoftDeletes usage, renaming a relationship), expectations like toBeTrashed() and toHaveRelationship('comments') fail with a message that names the domain concept, which is much clearer in CI than a raw boolean failure. This makes them good candidates for a small "model contract" test file:
it('keeps its public shape', function () {
$post = Post::factory()->create();
expect($post)
->toBeModel()
->toHaveRelationship('author');
});
Wrap repeated domain checks in your own expectations
If your app has recurring invariants (an order is paid, a tenant is active), define custom Pest expectations in tests/Pest.php and delegate to the package's primitives. This keeps domain language in one place while still using the plugin for the Laravel-level plumbing:
expect()->extend('toBePublished', function () {
return $this->toBeModel()->not->toBeTrashed();
});
Datasets + expectations for matrix testing
Because these are ordinary Pest expectations, they work inside ->with([...]) datasets. That is the cleanest way to assert "guest sees X, member sees Y, admin sees Z" without duplicating assertion blocks.
CI integration
Treat it like any other dev dependency: run composer update --prefer-lowest in a separate CI job. This package's own compatibility matrix (Pest majors × Testbench/Laravel versions) is where breakage will show up first — your app's tests are the second signal.
v2.6.2 is a maintenance release — upgrade is low risk
The only changes in this release are orchestra/testbench bumped from ^8.0|^9.0 to ^11.0.0 (a require-dev constraint, so it does not affect consumers of the package) and added Pest 5 support. There are no user-facing API changes, no deprecations, and no breaking changes. Safe to pull in on a normal composer update cycle.
Pest 5 support is the headline — but check your own Pest constraint
If you are moving your application to Pest 5, v2.6.2 is the first version of this plugin that supports it. If you are still on Pest 2/3/4, the plugin's Pest constraint should still cover you, but if Composer resolution fails after upgrading, pin defstudio/pest-plugin-laravel-expectations:2.6.1 until you finish the Pest upgrade. Do not upgrade Pest and this plugin in separate, unreviewed PRs — do them together so a red CI run has an obvious cause.
The Testbench bump is a signal about the package's CI, not your app
orchestra/testbench only exists in the package's own dev environment. Seeing it jump two majors means the maintainers are now testing against newer Laravel/Testbench versions and may have dropped CI coverage for older ones. That does not remove support for older Laravel in your app, but it does mean older combinations are less exercised. If you run an older Laravel, keep an eye on this package's release notes rather than assuming every release is tested against your version.
Expectations silently not existing looks like a syntax error
In Pest, calling an undefined expectation produces a confusing failure (Method ... does not exist) rather than "plugin not loaded." When that happens, check plugin discovery first — Composer autoload cache, require-dev presence, and any tests/Pest.php customization — before you start debugging the assertion itself.
Prefer the vendor source over documentation for exact signatures
Expectation names are close to English and easy to misremember. When an expectation takes an argument (for example a relationship name), confirm the exact parameter from vendor/.../src/Expectations/. This is faster than trial and error and avoids assuming a fluent chain exists.
Negation is available and useful, but assert the positive case where possible
->not->toBeTrashed() works, but if "not trashed" is a genuine invariant, a dedicated positive expectation (or your own expect()->extend) communicates intent better and produces a clearer failure message than a negated one.
Extension point: compose, don't fork
If an expectation you need is missing, add it via expect()->extend() in tests/Pest.php rather than forking the package. Forking means you inherit the maintenance burden of tracking Pest major releases — exactly the kind of churn v2.6.2 exists to absorb for you.
Keep it out of production dependencies
It is test-only. Verify it lands in require-dev and that your production build (composer install --no-dev) does not pull it in; a misplaced composer require is the most common way a test helper ends up shipped.
How can I help you explore Laravel packages today?