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

Testing Laravel Package

spiral/testing

Testing SDK for Spiral Framework packages. Provides a custom TestCase with a TestApp so you can test packages without a full application setup. Configure root directory and bootloaders, and keep test app config under tests/app. PHP 8.1+, Spiral 3.15+.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require spiral/testing --dev

Ensure your project meets the requirements: PHP 8.1+ and Spiral Framework 3.15+.

  1. Basic TestCase Structure: Create a base test class extending Spiral\Testing\TestCase in your package's tests/src directory:

    namespace MyPackage\Tests;
    
    abstract class TestCase extends \Spiral\Testing\TestCase
    {
        public function rootDirectory(): string
        {
            return __DIR__.'/../../'; // Adjust path to your package root
        }
    
        public function defineBootloaders(): array
        {
            return [
                \MyPackage\Bootloaders\PackageBootloader::class,
            ];
        }
    }
    
  2. First Test: Use the fakeHttp() helper to test HTTP endpoints:

    use MyPackage\Tests\TestCase;
    
    final class MyFirstTest extends TestCase
    {
        public function test_index_route()
        {
            $response = $this->fakeHttp()->get('/');
            $response->assertOk();
        }
    }
    
  3. Run Tests:

    vendor/bin/phpunit
    

Key Entry Points

  • fakeHttp(): Simulate HTTP requests with middleware and routes.
  • fakeEventDispatcher(): Mock event listeners and assertions.
  • fakeQueue(): Test job processing and assertions.
  • assertConfigHasFragments(): Validate configuration values.

Implementation Patterns

1. HTTP Testing Workflow

Basic Requests:

$response = $this->fakeHttp()->get('/api/users');
$response->assertStatus(200);
$response->assertJson(['count' => 1]);

JSON Requests:

$response = $this->fakeHttp()->postJson('/api/users', [
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);
$response->assertCreated();

Middleware Control:

// Disable middleware for a request
$response = $this->fakeHttp()->withoutMiddleware()->get('/');

// Add custom middleware
$response = $this->fakeHttp()->withMiddleware(\App\Middleware\AuthMiddleware::class)->get('/admin');

File Uploads:

$response = $this->fakeHttp()->post('/upload', [
    'file' => new \Psr\Http\Message\UploadedFile(
        fopen(__DIR__.'/test.txt', 'r'),
        10,
        UPLOAD_ERR_OK,
        'test.txt',
        'text/plain'
    )
]);

2. Event Testing

Mocking Events:

protected function setUp(): void
{
    parent::setUp();
    $this->eventDispatcher = $this->fakeEventDispatcher();
}

public function test_event_listener()
{
    $this->eventDispatcher->assertListening(
        \MyPackage\Events\UserCreated::class,
        \MyPackage\Listeners\SendWelcomeEmail::class
    );
}

Asserting Dispatched Events:

public function test_event_dispatched()
{
    // Trigger an event (e.g., via a service)
    $this->app->make(\MyPackage\Services\UserService::class)->createUser('john');

    $this->eventDispatcher->assertDispatched(\MyPackage\Events\UserCreated::class);
    $this->eventDispatcher->assertDispatchedTimes(\MyPackage\Events\UserCreated::class, 1);
}

3. Queue Testing

Fake Queue Jobs:

public function test_queue_job()
{
    $this->fakeQueue()->assertPushed(
        \MyPackage\Jobs\SendEmailJob::class,
        static function (\MyPackage\Jobs\SendEmailJob $job) {
            return $job->email === 'john@example.com';
        }
    );
}

Processing Jobs:

public function test_job_processing()
{
    $this->fakeQueue()->push(new \MyPackage\Jobs\SendEmailJob('john@example.com'));
    $this->fakeQueue()->run();

    $this->fakeQueue()->assertProcessed(\MyPackage\Jobs\SendEmailJob::class);
}

4. Configuration Testing

Validate Config Fragments:

public function test_config_values()
{
    $this->assertConfigHasFragments('http', [
        'basePath' => '/',
        'headers' => [
            'Content-Type' => 'application/json',
        ],
    ]);
}

Dynamic Config Overrides:

public function test_config_override()
{
    $this->app->config(['my-package' => ['debug' => true]]);
    $this->assertTrue($this->app->config('my-package.debug'));
}

5. Console Command Testing

Assert Command Registration:

public function test_command_registered()
{
    $this->assertCommandRegistered(\MyPackage\Console\GenerateCommand::class);
}

Simulate Command Execution:

public function test_command_output()
{
    $output = $this->fakeConsole()->run(\MyPackage\Console\GenerateCommand::class, ['name' => 'test']);
    $this->assertStringContainsString('Generated test', $output);
}

6. Scope-Based Testing

Test in Specific Scopes (e.g., http):

use Spiral\Testing\Attribute\TestScope;

#[TestScope('http')]
public function test_http_scope()
{
    $response = $this->fakeHttp()->get('/');
    $response->assertOk();
}

Gotchas and Tips

Common Pitfalls

  1. Container State Leakage:

    • Avoid relying on the container state between tests. Use fakeHttp(), fakeQueue(), etc., to isolate scopes.
    • Fix: Reset the container or use @TestScope attributes to limit scope leakage.
  2. Middleware Scope Issues:

    • Middleware added via withMiddleware() may not persist across requests if not scoped correctly.
    • Fix: Use withoutMiddleware() sparingly and ensure middleware is properly bound in your bootloader.
  3. Configuration Overrides:

    • Config changes in one test may affect others. Use setUp() and tearDown() to reset config if needed.
    • Fix: Override config in setUp() and restore defaults in tearDown():
      protected function tearDown(): void
      {
          $this->app->config(['my-package' => []]); // Reset to defaults
          parent::tearDown();
      }
      
  4. Event Dispatcher Conflicts:

    • If your app doesn’t use Spiral’s EventDispatcher, fakeEventDispatcher() may throw errors.
    • Fix: Check if the dispatcher is bound before using it:
      if ($this->app->hasBinding(\Spiral\Event\EventDispatcherInterface::class)) {
          $this->eventDispatcher = $this->fakeEventDispatcher();
      }
      
  5. Queue Job Assertions:

    • assertPushed() and assertProcessed() are strict about job class names. Use anonymous functions for complex assertions:
      $this->fakeQueue()->assertPushed(
          \MyPackage\Jobs\SendEmailJob::class,
          static fn (\MyPackage\Jobs\SendEmailJob $job) => $job->priority > 0
      );
      

Debugging Tips

  1. Inspect HTTP Requests: Use FakeHttp::createRequest() to manually craft requests for debugging:

    $request = $this->fakeHttp()->createRequest('GET', '/api/users');
    $request->getUri()->withQuery('page=1');
    $response = $this->fakeHttp()->handleRequest($request);
    
  2. Log Container Bindings: Dump container bindings to debug scope issues:

    $this->app->bindings()->each(function ($binding, $abstract) {
        \Log::debug("Binding: {$abstract} => {$binding}");
    });
    
  3. Test Response Body: When assertions fail, assertStatus() now displays the response body for debugging:

    $response->assertStatus(200); // Shows body if status fails
    
  4. Interactive Commands: Non-interactive mode is enabled by default for tests. To simulate user input:

    $this->fakeConsole()->run(\MyPackage\Console\InteractiveCommand::class, [], [
        'input' => ['y'], // Simulate user input
    ]);
    

Extension Points

  1. Custom Assertions: Extend TestCase to add reusable assertions:

    abstract class TestCase extends \Spiral\Testing\TestCase
    {
        protected function assertResponseHasData(array $expectedData)
        {
            $response = $this->fakeHttp()->get('/api/data');
            $response->assertJson($expectedData);
        }
    }
    
  2. Mocking External Services: Use Spiral’s bind() to mock dependencies:

    protected function setUp(): void
    {
        parent
    
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