wyrihaximus/react-phpunit-run-tests-in-fiber
PHPUnit trait to run each test inside a PHP Fiber, making it easy to use ReactPHP async/await in tests. Includes optional per-test or per-class timeout attributes to fail slow tests (without stopping the running fiber).
Installation: Add the package to your Laravel project’s devDependencies:
composer require --dev wyrihaximus/react-phpunit-run-tests-in-fiber
Ensure your project meets the requirements: PHP 8.4+, PHPUnit 12+, and react/async (automatically included as a dependency).
First Use Case:
use PHPUnit\Framework\TestCase;
use WyriHaximus\React\PHPUnit\RunTestsInFibersTrait;
final class AsyncServiceTest extends TestCase
{
use RunTestsInFibersTrait;
#[test]
public function itAwaitsPromisesCorrectly(): void
{
$result = \React\Async\await(new \React\Promise\FulfilledPromise(true));
self::assertTrue($result);
}
}
php artisan test (or your preferred PHPUnit command).await() for promises in fiber-aware tests:
public function itHandlesAsyncOperations(): void
{
$promise = new \React\Promise\Promise(function ($resolve) {
$resolve('success');
});
self::assertEquals('success', \React\Async\await($promise));
}
React\EventLoop\LoopInterface and running tests in fibers.#[\WyriHaximus\React\PHPUnit\TimeOut(10)]
final class AsyncServiceTest extends TestCase
{
use RunTestsInFibersTrait;
}
#[\WyriHaximus\React\PHPUnit\TimeOut(2)]
public function itTimesOutQuickly(): void { ... }
GuzzleHttp\Promise\PromiseInterface or React\Http\Message\Response in fibers:
public function itFetchesDataAsync(): void
{
$client = new \React\Http\Client();
$response = \React\Async\await($client->send(\React\Http\Message\Request::create('https://api.example.com')));
self::assertEquals(200, $response->getStatusCode());
}
public function itProcessesJobsInFiber(): void
{
$job = new AsyncJob();
$result = \React\Async\await($job->handle());
self::assertTrue($result);
}
DatabaseTransactions trait). Fibers may interfere with transaction rollbacks.protected function setUp(): void
{
parent::setUp();
$this->withoutExceptionHandling();
$this->withoutTransactions(); // Critical for fiber tests
}
Fiber Context Leaks:
[TimeOut(5)]) and avoid infinite loops in tests.Conflicts with Laravel’s Sync Tools:
queue:work) uses a separate process and won’t interact with fibers.Http::fake() or Http::assertSent() may not work as expected in fiber tests.Database Transactions:
withoutTransactions() or avoid transactions in fiber tests.Promise Rejections:
await() calls in try-catch blocks:
try {
$result = \React\Async\await($promise);
} catch (\React\Promise\RejectedPromiseException $e) {
self::fail($e->getMessage());
}
Check Fiber Status:
public function itRunsInFiber(): void
{
self::assertNotNull(\React\Async\getCurrentFiber());
}
Timeout Debugging:
#[\WyriHaximus\React\PHPUnit\TimeOut(1)]
public function itTimesOut(): void
{
error_log('Test started at: ' . time());
\React\Async\await(new \React\Promise\Deferred());
}
Event Loop Conflicts:
setUp():
protected function setUp(): void
{
parent::setUp();
\React\EventLoop\Factory::create()->run();
}
Custom Timeouts:
TimeOut attribute to support dynamic timeouts (e.g., based on environment variables):
#[\WyriHaximus\React\PHPUnit\TimeOut(env('TEST_TIMEOUT', 10))]
Fiber-Specific Assertions:
protected function assertFiberIsRunning(): void
{
self::assertNotNull(\React\Async\getCurrentFiber());
}
Integration with Laravel Packages:
spatie/laravel-react, use this trait to test async features:
use Spatie\React\React;
public function itProcessesReactEvents(): void
{
$result = \React\Async\await(React::process());
self::assertTrue($result);
}
await inside another await) unless necessary.How can I help you explore Laravel packages today?