laramint/laravel-stress
Fire-and-forget HTTP stress testing for Laravel. Runs Guzzle request pools in a background subprocess to avoid deadlocks with php artisan serve, with an in-process fallback for multi-threaded servers. Returns JSON stats (throughput, percentiles, errors).
Installation:
composer require laramint/laravel-stress
Verify compatibility with your Laravel version (9+ recommended) and PHP 8.1+.
First Use Case: Test a local API endpoint with 20 requests, 5 concurrent:
use LaraMint\LaravelStress\StressTestRunner;
$runner = new StressTestRunner();
$result = $runner->run([
'method' => 'GET',
'url' => 'http://localhost:8000/api/users',
'count' => 20,
'concurrency' => 5,
]);
dd($result); // Inspect timing, success rate, etc.
Where to Look First:
StressTestRunner class: Core methods (run(), startBackground()).timing, statusDistribution, and errors fields.sys_get_temp_dir() for JSON results (e.g., lb_st_res_123.json).Synchronous Testing (Blocking) Ideal for CI/CD pipelines or local validation:
$result = $runner->run([
'method' => 'POST',
'url' => 'http://localhost/api/orders',
'count' => 100,
'concurrency' => 10,
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['product_id' => 1]),
]);
$this->assertGreaterThan(90, $result->successRate);
Background Testing (Non-Blocking) Use during development to avoid deadlocks:
$jobId = $runner->startBackground([
'method' => 'GET',
'url' => 'http://localhost/api/products',
'count' => 500,
'concurrency' => 20,
]);
// Poll results later:
$resultFile = sys_get_temp_dir() . "/lb_st_res_{$jobId}.json";
$result = json_decode(file_get_contents($resultFile));
Dynamic Test Configuration Generate test payloads programmatically:
$urls = ['/users', '/posts', '/comments'];
$configs = array_map(fn($url) => [
'method' => 'GET',
'url' => "http://localhost{$url}",
'count' => 50,
'concurrency' => 3,
], $urls);
Integration with Laravel Testing Extend PHPUnit/Pest tests:
use LaraMint\LaravelStress\StressTestRunner;
trait StressTestable {
protected StressTestRunner $runner;
protected function setUp(): void {
$this->runner = new StressTestRunner();
}
protected function runStressTest(array $config): array {
return $this->runner->run($config);
}
}
Artisan Command Wrapper Create a reusable CLI command:
// app/Console/Commands/StressTestCommand.php
namespace App\Console\Commands;
use LaraMint\LaravelStress\StressTestRunner;
use Illuminate\Console\Command;
class StressTestCommand extends Command {
protected $signature = 'stress:test {url} {--count=100} {--concurrency=10}';
protected $description = 'Run stress test on a given URL';
public function handle(StressTestRunner $runner) {
$result = $runner->run([
'method' => 'GET',
'url' => $this->argument('url'),
'count' => $this->option('count'),
'concurrency' => $this->option('concurrency'),
]);
$this->line(json_encode($result, JSON_PRETTY_PRINT));
}
}
Middleware Simulation: Attach Guzzle middleware to mimic Laravel’s middleware stack:
$runner->run([
'method' => 'GET',
'url' => 'http://localhost/api/protected',
'concurrency' => 5,
'options' => [
'on_stats' => function (GuzzleHttp\TransferStats $stats) {
// Custom logic (e.g., auth token injection)
},
],
]);
Environment-Specific Configs: Use Laravel’s config system to override defaults:
// config/stress.php
return [
'default_concurrency' => env('STRESS_CONCURRENCY', 5),
'timeout' => env('STRESS_TIMEOUT', 10),
];
CI/CD Pipeline Integration: Example GitHub Actions workflow:
jobs:
stress-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: composer install
- run: php artisan serve & sleep 5 && php artisan stress:test http://localhost:8000/api/users --count=200 --concurrency=20
Result Validation: Create custom assertions for test suites:
$this->assertStressTestPasses($result, 95); // Assert success rate > 95%
$this->assertLatencyBelow($result, 500); // Assert avg latency < 500ms
Subprocess Deadlocks
php artisan serve) is single-threaded.Temporary File Collisions
sys_get_temp_dir().// config/stress.php
'temp_dir' => storage_path('app/stress-results'),
Concurrency Limits
Middleware Bypass
'options' => [
'headers' => ['Authorization' => 'Bearer ' . $token],
],
Orphaned Processes
// app/Console/Commands/CleanupStressProcesses.php
public function handle() {
$processes = shell_exec('ps aux | grep lb_st_');
foreach (explode("\n", $processes) as $line) {
if (str_contains($line, 'lb_st_') && !str_contains($line, 'grep')) {
$pid = explode(' ', trim($line))[1];
posix_kill($pid, SIGTERM);
}
}
}
Enable Guzzle Debugging:
$runner->run([
'options' => [
'debug' => fopen('storage/logs/guzzle_debug.log', 'w'),
],
]);
Inspect Subprocess Output:
Redirect stderr to Laravel logs:
// Override StressTestRunner to log subprocess output
public function startBackground(array $config) {
$config['options']['sink'] = function ($body) {
Log::debug('Stress test output: ' . $body);
};
return parent::startBackground($config);
}
Validate URLs:
Ensure URLs are absolute (e.g., http://localhost:8000/api/users) and not relative (e.g., /api/users).
Timeout Handling: Default timeout is 10 seconds. Increase for slow endpoints:
'timeout' => 30, // 30 seconds
Body Encoding:
Ensure body is properly encoded for POST requests:
'body' => json_encode(['key' => 'value']),
'options' => [
'headers' => ['Content-Type' => 'application/json'],
],
HTTPS Support: For local HTTPS testing, use:
'url' => 'https://localhost:8000',
'options' => [
'verify' => false, // Disable SSL verification for local dev
How can I help you explore Laravel packages today?