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 Slow Test Detector Laravel Package

ergebnis/phpunit-slow-test-detector

Detect slow PHPUnit tests with an extension delivered as a Composer package or PHAR. Configure a global maximum duration and get a report of tests exceeding the threshold after each run—ideal for catching performance regressions in your suite.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require --dev ergebnis/phpunit-slow-test-detector
    
  2. Configure phpunit.xml: Add the extension to your PHPUnit config based on your PHPUnit version (see README for exact syntax). Example for PHPUnit 10+:
    <extensions>
        <bootstrap class="Ergebnis\PHPUnit\SlowTestDetector\Extension"/>
    </extensions>
    
  3. Run tests:
    ./vendor/bin/phpunit
    
    The extension will now report slow tests (default threshold: 500ms).

First Use Case: Identify Slow Tests

Run your test suite as usual. The extension will:

  • List tests exceeding the default 500ms threshold.
  • Show durations in a formatted table.
  • Highlight the slowest tests first.

Example output:

Detected 3 tests where the duration exceeded the global maximum duration (0.500).
# Duration Test
1    1.200 App\Tests\Feature\SlowIntegrationTest::testPaymentProcessing
2    0.850 App\Tests\Unit\LegacyDatabaseTest::testDeprecatedQuery

Implementation Patterns

Workflow Integration

  1. CI/CD Pipeline:

    • Add to your CI script to fail builds if slow tests exceed a threshold (e.g., maximum-count=5).
    • Example GitHub Actions step:
      - run: ./vendor/bin/phpunit --configuration=phpunit-slow.xml
      
      Where phpunit-slow.xml includes:
      <parameter name="maximum-count" value="3" type="int"/>
      <parameter name="maximum-duration" value="300" type="int"/>
      
  2. Team Onboarding:

    • Use the extension to teach new developers about test performance.
    • Pair slow tests with documentation (e.g., @slow PHPDoc tags).
  3. Test Suite Optimization:

    • Iterative Refactoring:
      • Run tests with --filter=SlowTestClass to isolate slow suites.
      • Refactor tests to use faster mocks or reduce external dependencies.
    • Baseline Tracking:
      • Log slow test durations over time to track regressions.

Configuration Patterns

  1. Per-Environment Thresholds: Use environment variables to override defaults in phpunit.xml:

    <parameter name="maximum-duration" value="${env.SLOW_TEST_THRESHOLD:-500}" type="int"/>
    

    Set in .env:

    SLOW_TEST_THRESHOLD=300  # Stricter in CI
    
  2. Test Suite-Specific Rules: Override thresholds for specific test suites:

    <testsuites>
        <testsuite name="integration">
            <directory>tests/Integration/</directory>
            <parameter name="maximum-duration" value="1000" type="int"/>
        </testsuite>
    </testsuites>
    
  3. Stderr Output: Redirect slow test reports to stderr for CI logs:

    <parameter name="stderr" value="true" type="bool"/>
    

Laravel-Specific Use Cases

  1. Database-Driven Tests:

    • Slow tests often involve database operations. Use the extension to:
      • Identify tests hitting slow queries (pair with Laravel Debugbar).
      • Optimize DatabaseMigrations or Feature\Payment tests.
  2. Queue/Job Tests:

    • Mock slow external services (e.g., Stripe, Mailgun) in tests.
    • Example:
      // tests/Feature/PaymentTest.php
      public function test_payment_processing() {
          $this->withoutExceptionHandling();
          // Mock slow API call
          $this->partialMock(Stripe::class, function ($mock) {
              $mock->shouldReceive('charges.create')->andReturnUsing(fn() => sleep(1));
          });
      }
      
  3. Artisan Command Tests:

    • Slow tests often involve CLI commands. Use the extension to:
      • Refactor Artisan::call() tests to avoid real filesystem/database operations.
      • Example:
        // tests/Feature/CommandTest.php
        public function test_slow_command() {
            Storage::fake(); // Avoid real filesystem I/O
            $this->artisan('command:slow')->assertExitCode(0);
        }
        

Gotchas and Tips

Pitfalls

  1. False Positives:

    • Issue: Tests using sleep() or external APIs may legitimately be slow.
    • Fix: Exclude known slow tests via @skip or adjust thresholds:
      /** @skip Slow due to external API */
      public function test_external_api() { ... }
      
  2. CI Flakiness:

    • Issue: Network-dependent tests may fail intermittently in CI.
    • Fix: Use stderr output to debug:
      <parameter name="stderr" value="true" type="bool"/>
      
  3. PHPUnit Version Mismatch:

    • Issue: Incorrect extension configuration for your PHPUnit version.
    • Fix: Verify your PHPUnit version and use the correct syntax (e.g., bootstrap vs. extension tag).

Debugging Tips

  1. Log Raw Durations: Add a custom listener to log all test durations (for debugging):

    // tests/Listeners/DurationLogger.php
    use PHPUnit\Runner\AfterLastTestHook;
    use PHPUnit\Runner\TestListener;
    
    class DurationLogger implements TestListener, AfterLastTestHook {
        public function endTest(PHPUnit\Framework\Test $test, $time) {
            file_put_contents(
                'test_durations.log',
                sprintf("%s: %.3f\n", $test->getName(), $time),
                FILE_APPEND
            );
        }
    }
    

    Register in phpunit.xml:

    <listeners>
        <listener class="App\Tests\Listeners\DurationLogger"/>
    </listeners>
    
  2. Isolate Slow Tests: Run a single slow test with:

    ./vendor/bin/phpunit --filter=testPaymentProcessing
    

Extension Points

  1. Custom Threshold Logic: Extend the extension by implementing your own Ergebnis\PHPUnit\SlowTestDetector\Extension class:

    use Ergebnis\PHPUnit\SlowTestDetector\Extension as BaseExtension;
    
    class CustomExtension extends BaseExtension {
        protected function getMaximumDuration(): int {
            return (int) getenv('CUSTOM_THRESHOLD') ?: 500;
        }
    }
    

    Register in phpunit.xml:

    <extensions>
        <bootstrap class="App\Tests\Extensions\CustomExtension"/>
    </extensions>
    
  2. Slack/Teams Notifications: Parse the slow test report and send alerts:

    // scripts/send_slow_test_alert.php
    $output = shell_exec('phpunit --configuration=phpunit-slow.xml 2>&1');
    if (str_contains($output, 'Detected')) {
        $message = "Slow tests detected!\n```$output```";
        file_get_contents('https://hooks.slack.com/services/...', 'POST', [
            'Content-Type' => 'application/json',
            'data' => json_encode(['text' => $message])
        ]);
    }
    

Performance Tips

  1. Avoid Global State: Slow tests often involve shared state (e.g., static caches). Use dependency injection:

    // Bad: Static cache
    public function test_slow_cache() {
        Cache::remember('key', 3600, fn() => sleep(2));
    }
    
    // Good: Mock cache
    public function test_fast_cache() {
        $this->mock(Cache::class)->shouldReceive('remember')->andReturn('value');
    }
    
  2. Parallelize Independent Tests: Use PHPUnit’s --parallel flag to offset slow test impact:

    ./vendor/bin/phpunit --parallel
    
  3. Warm Up Dependencies: Initialize slow dependencies (e.g., database connections) outside tests:

    // tests/bootstrap.php
    DB::connection()->getPdo(); // Force connection early
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4
php-http/client-implementation
phpcr/phpcr-implementation
cucumber/gherkin-monorepo
haydenpierce/class-finder
psr/simple-cache-implementation