Weave Code
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

Async Test Utilities Laravel Package

wyrihaximus/async-test-utilities

Async testing utilities for PHP/React: extend AsyncTestCase to run each PHPUnit test inside a Fiber with a default 30s timeout. Includes TimeOut attribute (class/method), plus helpers like random namespaces/directories and callable expectation utilities.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require wyrihaximus/async-test-utilities --dev
    

    Add to your composer.json under require-dev if not using globally.

  2. Basic Test Case: Extend WyriHaximus\AsyncTestUtilities\AsyncTestCase in your test file:

    use WyriHaximus\AsyncTestUtilities\AsyncTestCase;
    
    final class MyAsyncTest extends AsyncTestCase
    {
        public function testBasicAsync(): void
        {
            // Test logic here
        }
    }
    
  3. First Use Case: Test asynchronous behavior with Loop::futureTick and assertions:

    public function testAsyncOutput(): void
    {
        self::expectOutputString('test');
    
        Loop::futureTick(static function (): void {
            echo 'test';
        });
    }
    

Key Features to Explore First

  • Timeout Management: Use the [TimeOut] attribute to control test execution time.
  • Callable Assertions: expectCallableOnce() and expectCallableExactly() for verifying async callbacks.
  • Random Directories: Utilize $this->getRandomDirectory() for file storage tests.

Implementation Patterns

Core Workflows

1. Async Test Execution

  • All tests run in a fiber, enabling non-blocking async operations.
  • Use Loop::futureTick() to schedule async tasks:
    public function testAsyncTask(): void
    {
        $result = $this->expectCallableOnce();
    
        Loop::futureTick(static function () use ($result): void {
            $result('async-data');
        });
    }
    

2. Timeout Management

  • Set class-level or method-level timeouts with the [TimeOut] attribute (in seconds):
    #[TimeOut(5)] // Class-level timeout (5s)
    final class MyTest extends AsyncTestCase
    {
        #[TimeOut(1)] // Overrides class timeout for this method
        public function testFastTimeout(): void
        {
            // ...
        }
    }
    

3. Callable Assertions

  • Verify async callbacks are invoked the expected number of times:
    public function testCallbackInvocations(): void
    {
        $callback = $this->expectCallableExactly(2);
    
        Loop::futureTick($callback);
        Loop::futureTick($callback);
        // Fails if called more/less than 2 times
    }
    

4. File Storage Testing

  • Use $this->getRandomDirectory() for isolated file operations:
    public function testFileOperations(): void
    {
        $dir = $this->getRandomDirectory();
        file_put_contents($dir.'/test.txt', 'data');
        $this->assertFileExists($dir.'/test.txt');
    }
    

5. Integration with ReactPHP

  • Leverage ReactPHP’s await() and async() for complex async flows:
    public function testReactIntegration(): void
    {
        $promise = async(static function (): string {
            return await(sleep(1)) . 'done';
        });
    
        $this->assertEquals('done', await($promise));
    }
    

Integration Tips

Laravel-Specific Patterns

  1. Service Container Integration: Bind async test utilities to Laravel’s container for reusable test setups:

    // In a TestServiceProvider
    $this->app->bind(AsyncTestCase::class, function () {
        return new AsyncTestCase();
    });
    
  2. Database Transactions: Combine with Laravel’s DatabaseTransactions trait for async DB tests:

    use Illuminate\Foundation\Testing\DatabaseTransactions;
    
    final class AsyncDatabaseTest extends AsyncTestCase
    {
        use DatabaseTransactions;
    
        public function testAsyncDbOperation(): void
        {
            // ...
        }
    }
    
  3. Event Testing: Test async event listeners with expectCallableOnce():

    public function testAsyncEventListener(): void
    {
        $listener = $this->expectCallableOnce();
        event(new MyEvent());
        // Assert listener was called
    }
    
  4. Queue Testing: Simulate async queue jobs:

    public function testAsyncQueueJob(): void
    {
        $job = $this->expectCallableOnce();
        dispatch(new MyJob($job));
    }
    

Gotchas and Tips

Pitfalls

  1. Timeout Misconfiguration:

    • Issue: Tests hang if timeouts are too short for async operations.
    • Fix: Start with [TimeOut(30)] (default) and adjust per test.
    • Debug: Use self::markTestSkipped() to debug flaky async tests.
  2. Callable Assertion Leaks:

    • Issue: Unused callables may cause false positives.
    • Fix: Store callables in class properties or use afterTest() to reset:
      protected function afterTest(): void
      {
          $this->callbacks = []; // Reset tracked callables
      }
      
  3. Fiber Context:

    • Issue: Global state (e.g., static vars) may leak between tests.
    • Fix: Avoid globals; use dependency injection or reset state in setUp().
  4. ReactPHP Loop Conflicts:

    • Issue: Multiple event loops can cause race conditions.
    • Fix: Ensure tests run in isolated fibers (handled by AsyncTestCase).
  5. File Permissions:

    • Issue: $this->getRandomDirectory() may fail if temp dir lacks permissions.
    • Fix: Use sys_get_temp_dir() for fallback:
      $dir = $this->getRandomDirectory() ?: sys_get_temp_dir();
      

Debugging Tips

  1. Log Async Output: Use self::expectOutputString() to debug async echoes:

    public function testAsyncEcho(): void
    {
        self::expectOutputString('debug:start');
        Loop::futureTick(static function (): void {
            echo 'debug:start';
        });
    }
    
  2. Manual Timeout Adjustment: Override the default timeout dynamically:

    public function testWithDynamicTimeout(): void
    {
        $this->setTimeout(10); // 10s timeout
        // ...
    }
    
  3. Assertion Failures:

    • Callables not invoked? Check if they’re passed correctly to Loop::futureTick().
    • Use self::assertTrue($this->callableWasInvoked()) for custom checks.

Extension Points

  1. Custom Assertions: Extend AsyncTestCase to add domain-specific assertions:

    final class ApiAsyncTest extends AsyncTestCase
    {
        protected function assertApiResponse(array $expected): void
        {
            // Custom logic
        }
    }
    
  2. Mocking Async Services: Replace Loop::futureTick() with a mock for isolated testing:

    public function testMockedAsync(): void
    {
        $mock = $this->createMock(LoopInterface::class);
        $mock->method('futureTick')->willReturnCallback($this->expectCallableOnce());
        // Inject $mock into your async service
    }
    
  3. Parallel Test Execution: Use AsyncTestCase alongside pestphp/pest-plugin-parallel for faster test suites:

    // pest.php
    uses(AsyncTestCase::class)->in('tests/Async');
    
  4. Custom Timeouts: Override the default timeout logic in a subclass:

    final class CustomTimeoutTest extends AsyncTestCase
    {
        protected function getDefaultTimeout(): float
        {
            return 60; // 1 minute
        }
    }
    

Configuration Quirks

  1. PHPUnit Version:

    • Requires PHPUnit 12.x (check composer.json constraints).
    • Downgrade if using older versions (but expect breaking changes).
  2. ReactPHP Dependencies:

    • Ensure react/event-loop and react/promise are installed (handled by Composer).
  3. Fiber Support:

    • Requires PHP 8.1+ (fibers are experimental in earlier versions).
  4. Global State:

    • Avoid static or singleton services in tests; they may interfere with fiber isolation.
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata