Jak testuję mikroserwisy w Laravel — 243 testy, RabbitMQ i OAuth2
Opisuję strategię testowania dla 5 serwisów PHP: testy jednostkowe, feature testy z prawdziwą bazą, mockowanie RabbitMQ i testowanie flow OAuth2.
Pokrycie testami
| Serwis | Linie | Testy |
|---|---|---|
| Users | 87.8% | 54 |
| SSO | 83.7% | 25 |
| Blog | 80.1% | 104 |
| Admin | 73.0% | 28 |
| Frontend | 61.4% | 46 |
| Razem | ~78% | 243 |
Każdy serwis używa SQLite in-memory (DB_CONNECTION=sqlite, :memory:) do testów — bez potrzeby stawiania MySQL dla CI.
Testy feature — realna baza, nie mocki
Preferuję testy feature z prawdziwą bazą zamiast unit testów z mockami. Przykład testu BlogAPI:
class PostApiTest extends TestCase
{
use RefreshDatabase;
public function test_can_get_published_posts(): void
{
Post::factory()->count(5)->published()->create();
Post::factory()->count(3)->draft()->create();
$response = $this->getJson('/api/v1/public/posts');
$response->assertOk()
->assertJsonCount(5, 'data')
->assertJsonStructure([
'data' => [['id', 'title', 'slug', 'excerpt', 'published_at']],
'meta' => ['total', 'per_page', 'current_page'],
]);
}
}
Mockowanie RabbitMQ
Testy publisherów RabbitMQ nie powinny wymagać działającego brokera. Używam mock():
public function test_publishes_post_viewed_event(): void
{
$publisher = $this->mock(AnalyticsEventPublisher::class);
$publisher->shouldReceive('publishPostViewed')
->once()
->with(Mockery::type('string'), null);
$this->get('/post/' . $post->slug);
}
Testowanie konsumera RabbitMQ
Consumer to Artisan Command. Test symuluje odebranie wiadomości przez wywołanie handlera bezpośrednio:
public function test_handles_user_updated_event(): void
{
$author = Author::factory()->create(['user_id' => 42]);
$handler = app(UserEventsMessageHandler::class);
$handler->handle([
'event' => 'user.updated',
'user_id' => 42,
'name' => 'Nowe Imię',
'email' => 'nowy@email.com',
]);
$this->assertDatabaseHas('authors', [
'user_id' => 42,
'name' => 'Nowe Imię',
]);
}
Testowanie OAuth2 w SSO
Flow OAuth2 ma wiele kroków. Test end-to-end całego flow:
public function test_authorization_code_flow(): void
{
$client = Passport::client()->create([...]);
$user = User::factory()->create();
// Krok 1: GET /oauth/authorize
$response = $this->actingAs($user)
->get('/oauth/authorize?' . http_build_query([
'client_id' => $client->id,
'redirect_uri' => 'https://app.test/callback',
'response_type' => 'code',
]));
// Krok 2: POST approve
$approveResponse = $this->actingAs($user)
->post('/oauth/authorize', [...]);
$code = parse_url($approveResponse->headers->get('Location'), PHP_URL_QUERY);
parse_str($code, $params);
// Krok 3: Wymiana kodu na token
$tokenResponse = $this->post('/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $params['code'],
'client_id' => $client->id,
]);
$tokenResponse->assertOk()->assertJsonStructure(['access_token', 'refresh_token']);
}