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

Spectator Laravel Package

spurwork/spectator

Spectator is a lightweight Laravel package for observing outgoing HTTP requests, letting you inspect, log, and assert on the calls your app makes so you can debug integrations and write reliable tests without hitting external APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Spectator is a contract-testing package: it validates your Laravel HTTP tests against an OpenAPI 3 document, so the spec becomes an executable part of the test suite rather than stale documentation.

  1. Install as a dev dependency (it should never ship to production):
    composer require --dev hotmeteor/spectator
    
  2. Publish the config:
    php artisan vendor:publish --tag=spectator-config
    
  3. In config/spectator.php, register your spec under sources and — critically — set path_prefix if your API lives behind a prefix such as /api:
    'sources' => [
        'local' => base_path('openapi.json'),
    ],
    'path_prefix' => 'api',
    
  4. Point your test case at the spec in setUp():
    protected function setUp(): void
    {
        parent::setUp();
        Spectator::using('openapi.json');
    }
    
  5. Sanity-check coverage before writing assertions:
    php artisan spectator:routes
    
    As of v3.0.4 this command prints the resolved path including the configured prefix, which makes a misconfigured path_prefix immediately obvious instead of silently producing no matches.

First use case: take one existing feature test hitting GET /api/users/{id} and add the two contract assertions. If they pass, you have a working baseline; if they fail, the error message names the exact JSON path that diverged — fix the spec or the endpoint, then repeat per resource.

Implementation Patterns

Baseline happy path — the daily bread-and-butter assertion pair:

$response = $this->getJson('/api/users/1');

$response->assertValidRequest()
         ->assertValidResponse(200);

assertValidRequest() checks the outgoing test request against the spec (method, path params, query, headers, body); assertValidResponse() checks the actual response body/headers against the declared schema for that status code.

Contract-first workflow. Write or update the OpenAPI operation before the controller, then let the failing test define the shape. This is where Spectator earns its keep: the spec diff and the code diff land in the same PR and cannot drift apart unnoticed.

Layer contract and behaviour assertions. Spectator only proves shape; keep your normal Laravel assertions for semantics:

$response->assertValidResponse(200)
         ->assertJsonPath('data.0.email', '[email protected]');

$this->assertDatabaseHas('users', ['id' => 1]);

Negative-path testing. Pair the happy path with error-shape tests so your 4xx bodies are covered by the contract too:

$this->postJson('/api/users', [])->assertValidResponse(422);

Use the invalid-request/invalid-response assertions when you deliberately want to assert that something does not conform (e.g. proving a validation guard rejects malformed payloads).

Multiple specs. For a versioned or segmented API (openapi-v1.json, openapi-v2.json), flip the spec per test with Spectator::using(...) in the individual test or a trait, rather than a single global setUp.

Form and urlencoded endpoints. v3.0.4 makes an empty application/x-www-form-urlencoded body resolve to stdClass rather than []. Without that, an empty form POST validated against an object-typed request schema threw a 500 from the validator rather than a clean contract failure. If you test "submit empty form" cases (search filters, settings forms, unsubscribe flows), those tests are now viable — add them.

CI gate. Run the full suite on every PR; treat a spec/implementation mismatch as a failing build. Optionally add a composer test:contract script filtering on a Contract test suite so contract drift is reported separately from logic regressions.

Gotchas and Tips

  • path_prefix is the number one silent failure. If it's wrong, routes simply never match the spec and assertions pass vacuously or fail confusingly. Since v3.0.4, spectator:routes shows the resolved prefixed path — always eyeball that output after changing route groups or adding a versioned prefix like api/v2.
  • Content-Type must match the operation. A postJson() against a spec operation declared only as multipart/form-data (or vice versa) fails validation for reasons that look like schema errors. Declare every content type you actually accept in the spec.
  • Strict schemas bite on framework output. additionalProperties: false, missing required entries, and untyped nullable fields are the usual culprits when a genuinely correct response fails. Generate the schema from your API Resource/casts rather than hand-writing it, and keep nullable consistent on both sides.
  • Response examples help. Some assertions are easier to satisfy and debug when each status code carries an example; the validator's error messages then point at a concrete offending value.
  • Read the validation message, not the stack trace. Failures come back as a list of messages containing a JSON pointer (/data/0/id). Go straight to that node — it's almost always a type mismatch ("1" vs 1), a missing required key, or an unexpected extra key.
  • Spec parsing cost. The document is loaded per test process; keep one canonical spec per suite rather than dozens of small files, and prefer JSON over YAML if you want to avoid a YAML extension dependency in CI.
  • require-dev only. Keep Spectator out of production autoload; if you use --no-dev deploys this is automatic, but check any custom CI images that install with dev dependencies.
  • Extension points. The published config is the supported seam — sources, prefix, and default source — so resist editing the package itself; wrap Spectator::using() in a base test case or trait when you need per-suite defaults.
  • Upgrading to v3.0.4 is a no-op for most projects. Both changes are fixes: the empty-form-body handling and the improved spectator:routes output. No breaking changes, no config changes required — just composer update hotmeteor/spectator. The one behavioural difference to be aware of: if you had a test asserting a 500 (or skipping) empty form POSTs against an object schema, it will now validate normally and should be rewritten as a real contract assertion.
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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules