volt-test/laravel-performance-testing
A Laravel package for performance testing and benchmarking. Define scenarios, measure response times and database queries, and set assertions to catch slowdowns and regressions before they reach production.
Install as a dev dependency and keep it out of production:
composer require --dev volt-test/laravel-performance-testing
Look first at, in this order:
composer.json — confirm the supported Laravel and PHP matrix (v1.3.3 adds PHP 8.5 to the test matrix, so 8.4/8.5 are safe; check the constraint floors against your app).src/ — one or two traits plus a handful of support classes; skim ServerHealth/health-check classes since that is what v1.3.3 touched, and any Performance/Assert trait to see exactly what is exposed.config/ — if the package ships a config file, publish it (php artisan vendor:publish --tag=...) and read every key before tuning thresholds.Your first use case should be the smallest possible: one test that hits your hottest public route and asserts a duration budget and a query-count budget.
it('serves the homepage within budget', function () {
$this->get('/')
->assertPerformance(); // adjust to the actual helper names in the README
});
Run it once locally with Xdebug off to get a realistic baseline, then commit it. Do not start by wrapping your entire route table — you will spend the first week fighting flaky assertions instead of finding real regressions.
Separate the performance suite from the unit suite. Put performance tests in tests/Performance and wire a dedicated Pest/PHPUnit testsuite (or a @group performance annotation) so composer test stays fast and deterministic while composer test:perf runs the slow, environment-sensitive checks. Most teams run the perf suite on a schedule or on PRs targeting main, not on every push.
Adopt a budget, not a benchmark. Assert against an explicit ceiling that is generous enough to survive noisy CI hardware (e.g. "under 400ms", "under 25 queries") rather than a tight number copied from your laptop. The value of this package is catching a 10x regression, not a 5% one.
Use query counts and memory as the primary signals, wall-clock time as the secondary one. Query count and peak memory are far more stable across runners than elapsed milliseconds. A pattern that ages well:
it('does not N+1 the order index', function () {
Order::factory()->count(30)->create();
$this->actingAs($user)
->get('/orders')
->assertQueryCountLessThan(20); // illustrative
});
Drive many routes from a dataset. In Pest, a dataset of [uri, maxDuration, maxQueries] triples lets you cover a dozen endpoints in one test body and tune each independently. This scales far better than copy-pasting a test per route.
Warm up before measuring. The first request in a test process pays for container boot, config resolution, and view compilation. If the package's assertion measures the request you just made, either issue a throwaway request first or run the assertion against a cached-config, cached-route environment so the number reflects steady state.
Gate thresholds by environment. Read the budgets from config or from env() in the test itself, so CI runners (slow, shared) get looser numbers than a developer machine. Hardcoding one number guarantees someone disables the suite.
Put the server health check into your deploy pipeline. The class touched in v1.3.3 exists to validate the runtime environment. Run it as a post-deploy or pre-traffic step (artisan command or a small script) so OPcache settings, PHP version drift, and similar host-level misconfigurations surface before users find them.
Pair it with Laravel's own tooling. Use DB::listen / Model::preventLazyLoading() in the performance suite to turn lazy-loading into a hard failure, and let this package own the budget assertions. The two complement each other rather than overlapping.
composer update with no code changes required.TELESCOPE_ENABLED=true in .env.testing is the single most common cause of "why is this suddenly 3 seconds".APP_DEBUG=true inflates memory and time. Confirm the testing env has it off; the difference is routinely 2–3x.--filter, then dump the actual measurement rather than only the assertion message. Most failures are a single bad route (a missing with() on an eager load) rather than a systemic slowdown.tests/Performance/Concerns rather than forking. Keep custom thresholds in one config file so the whole team tunes them in one place.^1.3) and let Renovate/Dependabot open PRs — patches have historically been deprecation and compatibility fixes, which is exactly what you want flowing automatically on the PHP 8.4→8.5 upgrade path.How can I help you explore Laravel packages today?