0

Integration test — testing multiple real components working together

Beginner5 min read·eng-17-002
interview

Concept

Integration test — a test that verifies multiple real components working together. Unlike unit tests, integration tests use the real dependencies (real database, real service container, real cache).

What "integration" means: The INTEGRATION between components — how they work when combined. An integration test for OrderService with a real database verifies that the SQL queries actually work, that the schema is correct, that constraints are honored.

What integration tests catch that unit tests don't:

  • SQL queries are actually valid and return the expected data.
  • Schema migrations match the queries.
  • Cache invalidation actually works.
  • Middleware chains behave correctly.
  • Service provider bindings are correctly wired.

In Laravel:

  • extends Tests\TestCase — boots the full Laravel application.
  • use RefreshDatabase — runs migrations before each test, rolls back after.
  • use DatabaseTransactions — wraps each test in a transaction, rolls back after. Faster.

Integration test characteristics:

  • Slower than unit tests (DB setup, application boot).
  • More realistic than unit tests (real behavior, not mocked).
  • Fewer in number (slow to run, use strategically).

vs Feature tests: Integration tests focus on component-to-component behavior. Feature tests focus on full HTTP request/response cycles (user-facing features).

vs Unit tests: Integration tests allow real dependencies. Unit tests mock everything.

Code Example

php
<?php
// INTEGRATION TEST — tests OrderService with real database
namespace Tests\Integration;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class OrderServiceTest extends TestCase
{
    use RefreshDatabase; // migrate + rollback for each test

    private OrderService $service;

    protected function setUp(): void
    {
        parent::setUp();
        $this->service = app(OrderService::class); // real service with real dependencies
    }

    public function test_creates_order_in_database(): void
    {
        $user = User::factory()->create();

        $order = $this->service->place($user, [
            'items' => [['product_id' => 1, 'quantity' => 2, 'price' => 10.00]],
            'total' => 20.00,
        ]);

        // Assert the order exists in the real database
        $this->assertDatabaseHas('orders', [
            'id'      => $order->id,
            'user_id' => $user->id,
            'total'   => 20.00,
            'status'  => 'pending',
        ]);

        // Assert items exist in the real database
        $this->assertDatabaseHas('order_items', [
            'order_id'   => $order->id,
            'product_id' => 1,
            'quantity'   => 2,
        ]);
    }

    public function test_cancels_order_and_restores_stock(): void
    {
        $user    = User::factory()->create();
        $product = Product::factory()->create(['stock' => 10]);

        $order = $this->service->place($user, [
            'items' => [['product_id' => $product->id, 'quantity' => 3, 'price' => 5.00]],
            'total' => 15.00,
        ]);

        $this->service->cancel($order);

        // Verify status changed in DB
        $this->assertDatabaseHas('orders', ['id' => $order->id, 'status' => 'cancelled']);

        // Verify stock was restored in DB
        $this->assertEquals(10, $product->fresh()->stock); // stock back to original
    }

    public function test_cannot_place_order_with_insufficient_stock(): void
    {
        $user    = User::factory()->create();
        $product = Product::factory()->create(['stock' => 1]); // only 1 in stock

        $this->expectException(\App\Exceptions\InsufficientStockException::class);

        $this->service->place($user, [
            'items' => [['product_id' => $product->id, 'quantity' => 5]], // want 5, only 1 available
            'total' => 50.00,
        ]);
    }

    public function test_dispatches_event_after_order_placed(): void
    {
        \Event::fake([\App\Events\OrderPlaced::class]);

        $user  = User::factory()->create();
        $order = $this->service->place($user, ['items' => [], 'total' => 0]);

        \Event::assertDispatched(\App\Events\OrderPlaced::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });
    }
}