Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Testing Laravel Package

illuminate/testing

Illuminate Testing is Laravel’s official testing package, offering the base TestCase plus expressive helpers, HTTP and database assertions, and fakes for events, queues, mail, and more so you can write reliable feature and unit tests quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

The honest first step: you almost certainly already have this code installed. illuminate/testing is a read-only subtree split of laravel/framework, so composer require laravel/framework gives you the identical source under vendor/laravel/framework/src/Illuminate/Testing. Only reach for composer require --dev illuminate/testing when you specifically want that narrow split (auditing the component, or a package that wants the minimal dependency surface).

Be aware of where the line falls: this split ships the Illuminate\Testing\* namespace — TestResponse, AssertableJsonString, Fluent\AssertableJson, TestView, TestComponent, TestComponentCollection, Exceptions\*. The base TestCase, MakesHttpRequests, and the traits you actually use (RefreshDatabase, DatabaseTransactions, WithFaker) live in Illuminate\Foundation\Testing. Day to day you extend Tests\TestCase and interact with this package through TestResponse.

Minimal path:

  1. php artisan make:test OrderIndexTest — generates a class extending Tests\TestCase.
  2. Assert against a route, don't just call it.
  3. php artisan test --filter=OrderIndexTest.

Files worth reading first in the vendor tree:

  • Illuminate/Testing/TestResponse.php — the assertion surface you'll live in (assertOk, assertJson, assertJsonPath, assertSee, assertSessionHas, assertRedirect).
  • Illuminate/Testing/Fluent/AssertableJson.php — the scoped fluent JSON API (has, where, etc, scope).
  • Illuminate/Testing/AssertableJsonString.php — the same assertions against a raw JSON string, no HTTP response required.
  • Illuminate/Testing/TestView.php / TestComponent.php — asserting on Blade output and components.

First use case — assert structure, not a frozen payload:

public function test_orders_index_is_paginated(): void
{
    $user = User::factory()->has(Order::factory()->count(3))->create();

    $this->actingAs($user)
        ->getJson('/api/orders')
        ->assertOk()
        ->assertJsonPath('meta.per_page', 15)
        ->assertJsonCount(3, 'data');
}

Implementation Patterns

Prefer scoped fluent JSON assertions over array literals. assertJson([...]) is a subset match and reads poorly once payloads grow. AssertableJson lets you pin the fields that matter and explicitly ignore the rest:

->assertJson(fn (AssertableJson $json) => $json
    ->has('data', 3)
    ->has('data.0', fn (AssertableJson $json) => $json
        ->where('status', 'pending')
        ->whereType('total', 'integer')
        ->etc()            // tolerate extra keys, but declare it
    )
    ->has('meta')
);

Drive console commands through $this->artisan(), which returns a PendingCommand from this ecosystem — chain expectsOutput, expectsQuestion, expectsChoice, assertExitCode. Use $this->artisan(...)->run() when you need the exit code in a variable rather than an assertion.

Boundary fakes are the highest-leverage helpers. Bus::fake(), Queue::fake(), Event::fake(), Notification::fake(), Mail::fake(), Http::fake() (the Illuminate\Support\Facades\Http client, not this package, but it is what you'll pair with TestResponse). Assert intent — Bus::assertDispatched(SendReceipt::class, fn ($job) => $job->order->is($order)) — rather than asserting side effects you don't own.

Mock and spy through the container, not new:

$this->mock(PaymentGateway::class, fn ($m) => $m
    ->shouldReceive('charge')->once()->andReturn(new Receipt(['id' => 'ch_1']))
);

$this->spy(Logger::class)->shouldHaveReceived('info');

Database strategy is a per-suite decision. RefreshDatabase migrates once per process and wraps each test in a transaction — the default. DatabaseTransactions skips migration and is faster when your schema is already seeded by CI. Reserve DatabaseMigrations for tests that genuinely need a clean migrate (e.g., migration tests themselves).

Register custom assertions as macros. TestResponse is Macroable, which is the clean extension point for domain-specific assertions:

// app/Providers/TestingServiceProvider.php (registered only in local/testing)
TestResponse::macro('assertOrderShipped', function () {
    /** @var TestResponse $this */
    return $this->assertJsonPath('data.shipped_at', fn ($v) => $v !== null);
});

For package development, depend on illuminate/testing only if you need TestResponse-typed signatures; otherwise require the framework in require-dev and let consumers bring their own.

Gotchas and Tips

  • Version alignment is non-negotiable. All illuminate/* splits must share a major (13.x with 13.x). A mismatch surfaces as a confusing Composer resolution failure or, worse, a missing method at runtime. In a Laravel app this is a non-issue — you get one framework version.
  • Never file issues or PRs against the split. It's a read-only subtree; changes must land in laravel/framework. Anything you "fix" locally will be overwritten.
  • Container state leaks across tests. $this->app->instance(...), facade swaps via Facade::swap(), and Config::set() persist unless the app is refreshed. Prefer $this->app->refreshApplication() in setUp for suites that mutate bindings, and remember RefreshDatabase resets the DB, not the container.
  • assertJson is a subset match — it is not assertExactJson. Extra keys pass silently. If you need to catch accidental payload growth, say so explicitly with ->etc() in the fluent API or use assertExactJson.
  • assertJsonStructure wildcards are shallow. ['data' => [['id', 'total']]] asserts the shape of the first element only; it does not verify every item. Use ->has('data', $count) or each() for real coverage.
  • dump() / dd() exist on TestResponse and print the response body mid-chain, which is the fastest way to debug an assertion failure that only says "failed asserting that two strings are equal." You can also ->dumpHeaders() and ->dumpSession().
  • withoutExceptionHandling() first, always, when a test fails opaquely. By default exceptions are rendered into a 500 response; disabling the handler surfaces the real stack trace and stack frame inside your app.
  • Don't call Faker::create() yourself if you use WithFaker — use $this->faker so the seed stays consistent within a test.
  • assertSee escapes by default. assertSee('A & B') will not find A & B in HTML-rendered output; pass false as the second argument or use assertSeeText.
  • Streamed and file responses have partial support. TestResponse assertions like assertJson don't apply to StreamedResponse; use $response->streamedContent() and assert on the string.
  • Custom assertion failure messages are worth the keystrokes. Wrap multi-step checks in a helper that passes $message through to assertTrue — future you, reading CI output, will thank present you.
  • Extension point beyond macros: subclass Tests\TestCase with your own assertDomainInvariant() helpers, or add traits per concern (InteractsWithBilling, FreezesTime). Keep them in tests/, not in app/, so they never ship to production.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
thomas-0816/doctrine-dbal-duckdb
moyisoft/laravel-admin
oxygen/mod-pages
oxygen/mod-import-export
oxygen/mod-media
ksoft/laravel-contentbuilderjs
ourgoldpackages/exchange
mikkimike/laravel-exchange1c
wandesnet/mercadopago-laravel
tsfcorp/email
larafony/core
laravel-enso/currencies
tartan/laravel-online-payment
gam/laravel-sat-catalogs
cloudcreativity/laravel-stripe
abdulelahragih/querybuilder
wedevbr/celcoin-laravel
surazdott/nepali-date
devsquad-cockpit/laravel
coding-socks/laravel-upload-handler