Testing API Calls with Laravel HTTP Client Fakes
When Laravel code sends an HTTP request to an external API, a test that calls the real service relies on both the…
Laravel package maintainers need tests that check how their code behaves inside Laravel, across the PHP and Laravel versions the package supports. Continuous integration (CI) runs those checks automatically when someone pushes a change or opens a pull request. Passing checks give maintainers evidence that the package works with the tested PHP and Laravel versions.
Use Orchestra Testbench to set up the Laravel context for package tests, then define a GitHub Actions matrix with one job for each supported PHP and Laravel combination. Treat failures on those combinations as release blockers. A matrix is a set of configuration values that GitHub combines into separate jobs; Testbench supplies the Laravel application context those jobs need.
A package’s tests need to exercise the framework behavior the package depends on. That behavior may include service-provider bootstrapping or routes, as well as configuration or migrations. Laravel’s package-development documentation recommends Testbench for running package tests as if the package were installed in a Laravel application.
Match each Laravel major version with a compatible Testbench major version. The Testbench compatibility table maps Laravel 12 to Testbench 10 and Laravel 13 to Testbench 11, among other combinations. That mapping is a starting point: check the exact package constraints and Testbench release requirements before adding a combination to CI.
The required matrix should reflect the compatibility the package promises to consumers. If the package supports Laravel 12 and 13, test both. Choose PHP versions allowed by the package’s Composer requirements and by each Laravel version. Avoid adding combinations that Composer cannot resolve under those constraints.
This example tests two Laravel versions on two PHP versions. Composer runs each combination with either the lowest allowed dependency versions or stable releases, for eight jobs total.
name: Package tests
on:
push:
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
stability: [prefer-lowest, prefer-stable]
include:
- php: "8.3"
laravel: "12.*"
testbench: "10.*"
- php: "8.3"
laravel: "13.*"
testbench: "11.*"
- php: "8.4"
laravel: "12.*"
testbench: "10.*"
- php: "8.4"
laravel: "13.*"
testbench: "11.*"
name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }} / ${{ matrix.stability }}
steps:
- uses: actions/checkout@v7
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, pdo, sqlite, pdo_sqlite
coverage: none
- name: Validate Composer metadata
run: composer validate --strict
- name: Select framework and Testbench versions
run: >
composer require
"laravel/framework:${{ matrix.laravel }}"
"orchestra/testbench:${{ matrix.testbench }}"
--no-interaction
--no-update
- name: Resolve and install dependencies
run: >
composer update
--${{ matrix.stability }}
--prefer-dist
--no-interaction
- name: Run tests
run: vendor/bin/phpunit
The workflow selects Laravel and Testbench constraints before Composer resolves dependencies. The SQLite extensions shown are examples for packages whose tests use SQLite; configure the PHP extensions and database services your own tests require. The PHP setup action documents its PHP-version and extension options.
GitHub’s matrix fail-fast setting defaults to true, which cancels other matrix jobs after one fails. Setting it to false lets the remaining jobs finish, giving maintainers a fuller view of which combinations failed. GitHub documents both matrix behavior and job variations. The action references in the example should also be checked against the repository’s action-pinning and security policy.
composer install uses the exact dependency versions in composer.lock when that file exists. It is appropriate for a job that checks a pinned development dependency set:
composer install --prefer-dist --no-interaction
composer update resolves dependencies from the constraints in composer.json. Use it when a matrix changes Laravel or Testbench versions, or when you want to test the dependency ranges consumers may install. The example uses composer update for that reason. The prefer-lowest mode asks Composer to favor the lowest versions allowed by the constraints; prefer-stable favors stable releases.
For libraries, a committed lock file can help maintainers reproduce their development environment, but it does not set the versions Composer installs in applications that require the library. Composer describes this distinction in its library guidance. A separate locked-dependency job can use composer install while matrix jobs use composer update. Do not use --ignore-platform-reqs to force an incompatible PHP or extension combination to install; that hides a platform mismatch rather than testing it.
Treat a failing test or dependency-resolution error on a PHP and Laravel combination the package claims to support as a release blocker. The same applies to failures in supported package behavior, such as service-provider registration, configuration, routes, commands, or migrations. Testbench is designed to exercise package code in a Laravel context, so failures there directly challenge the package’s compatibility claim.
Also block a release when a required project check fails. For example, if the project requires Composer metadata validation, formatting, static analysis, or a coverage threshold, keep that check in CI and enforce its result. Composer recommends running composer validate before committing metadata changes and before tagging a release.
An experimental job for an unsupported future version is different: its failure does not establish a regression in a promised configuration. Label exploratory jobs clearly and keep them separate from required release checks. If a CI runner outage prevents a supported job from completing, the result is inconclusive; rerun or investigate the job rather than treating it as a pass.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com