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

Phpunit Asynchronicity Laravel Package

matthiasnoback/phpunit-asynchronicity

PHPUnit/Behat helper for testing asynchronous behavior. Provides assertEventually() to retry a callable until assertions pass or a timeout occurs—useful for waiting on files, processes, or UI updates, with configurable timeout and polling interval.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the package via Composer:

    composer require --dev matthiasnoback/phpunit-asynchronicity
    

    Requires PHPUnit 9.5+ and PHP 8.1+.

  2. First Use Case Test an async queue job or event listener:

    use MatthiasNoback\PHPUnitAsynchronicity\AsyncTestTrait;
    
    class AsyncJobTest extends TestCase
    {
        use AsyncTestTrait;
    
        public function testJobProcessesAsync()
        {
            $this->dispatchSync(new ProcessOrderJob(123));
    
            $this->assertEventuallyTrue(fn() => Order::find(123)->processed_at !== null);
        }
    }
    
  3. Key Files

    • AsyncTestTrait.php (core assertions)
    • AsyncTestCase.php (pre-configured base class)
    • README.md (examples + edge cases)

Implementation Patterns

Common Workflows

1. Asserting Async Side Effects

// Test a delayed notification
$this->assertEventuallyTrue(
    fn() => User::find(1)->notified_at !== null,
    1000, // timeout (ms)
    100   // interval (ms)
);

2. Testing Queue Workers

// Dispatch and verify processing
$this->dispatchSync(new SendEmailJob($user));
$this->assertEventually(
    fn() => Mail::assertSent(SendWelcomeEmail::class),
    5000
);

3. Event Listeners

// Verify async event handling
event(new OrderPlaced($order));
$this->assertEventuallyTrue(
    fn() => $order->refresh()->status === 'shipped'
);

4. Integration with Laravel

// Override default queue connection for tests
protected function getAsyncTestConfiguration(): AsyncTestConfiguration
{
    return AsyncTestConfiguration::create()
        ->withQueueConnection('test_queue');
}

Best Practices

  • Use assertEventually for non-deterministic async operations.
  • Prefer assertEventuallyTrue/False for boolean checks.
  • Set reasonable timeouts (default: 10s) based on your queue worker speed.
  • Mock external services when testing async logic to avoid flakiness.

Gotchas and Tips

Pitfalls

  1. Flaky Tests

    • Issue: Async assertions may fail intermittently due to timing.
    • Fix: Increase timeout/interval or mock slower dependencies.
    • Example:
      $this->assertEventually(
          fn() => $this->app->make(Logger::class)->hasErrors(),
          15000, // 15s timeout
          500    // 500ms interval
      );
      
  2. Queue Worker Not Running

    • Issue: Tests pass locally but fail in CI (no queue worker).
    • Fix: Use php artisan queue:work --daemon or mock the queue.
    • Alternative: Use dispatchSync() for critical paths in tests.
  3. State Pollution

    • Issue: Async operations may leave test state dirty between tests.
    • Fix: Reset state in tearDown() or use transactions:
      public function setUp(): void
      {
          parent::setUp();
          $this->beginDatabaseTransaction();
      }
      
  4. Timeout Too Short

    • Issue: Assertions fail with TimeoutException.
    • Fix: Monitor queue worker speed and adjust:
      $this->assertEventually(/* ... */, 30000); // 30s timeout
      

Debugging Tips

  • Log Async State:
    $this->assertEventually(
        fn() => tap($this->getAsyncState(), fn($state) => $this->info($state)),
        10000
    );
    
  • Check Queue Backlog:
    $this->assertEventuallyTrue(
        fn() => Queue::size('default') === 0
    );
    
  • Use assertEventuallyMatches() for complex conditions:
    $this->assertEventuallyMatches(
        fn() => Order::find(123),
        fn(Order $order) => $order->status === 'completed' && $order->processed_at > now()->subMinute()
    );
    

Extension Points

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

    trait MyAsyncAssertions
    {
        protected function assertOrderShipped(int $orderId): void
        {
            $this->assertEventuallyTrue(
                fn() => Order::find($orderId)->status === 'shipped'
            );
        }
    }
    
  2. Override Configuration Customize timeouts/intervals per test class:

    class SlowAsyncTest extends AsyncTestCase
    {
        protected function getAsyncTestConfiguration(): AsyncTestConfiguration
        {
            return parent::getAsyncTestConfiguration()
                ->withTimeout(60000) // 60s
                ->withInterval(2000); // 2s
        }
    }
    
  3. Mock Async Operations For unit tests, bypass async entirely:

    $this->partialMockBuilder(Queue::class)
        ->disableOriginalConstructor()
        ->disableOriginalClone()
        ->setMethods(['push'])
        ->getMock()
        ->expects($this->once())
        ->method('push')
        ->with($this->equalTo(new ProcessOrderJob(123)));
    
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.
cadot.eu/make
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky