Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 38 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20264 min read

Advanced Testing: Integration Tests in Laravel

Master integration testing in Laravel. Learn how to manage database state, verify multi-component workflows, and ensure your application logic works in unison.

LaravelTestingIntegration TestingPHPDatabasebackend

Previously in this course, we explored Testing Events and Jobs in Laravel to ensure our asynchronous processes were firing correctly. Now, we're zooming out to the "Integration" level, where we verify that our services, repositories, and database constraints actually play nice together.

When you unit test, you mock everything. But in a real-world Laravel application, your code lives and dies by its side effects: a row created in a table, a relationship established between models, and a state change that ripples across your system. Integration testing is about ensuring these connections aren't just theoretically correct, but practically functional.

Why Integration Testing Matters

In our project board application, a "Task Creation" workflow involves the TaskService, the TaskRepository, and several database-level constraints (like project ownership). If we only mock the repository, we might miss a scenario where a database constraint violation—like a foreign key mismatch—breaks the user experience.

Integration testing allows us to:

  1. Verify Database Persistence: Ensure data is actually saved correctly.
  2. Test Eloquent Relationships: Confirm that task->project returns the expected instance.
  3. Validate Business Workflows: Ensure that the sequence of operations (e.g., creating a task, assigning it to a user, and updating the project's task count) happens atomically.

Managing Database State

The biggest pain point in integration testing is database pollution. You don't want tests to leak state into each other. Laravel handles this natively using the Illuminate\Foundation\Testing\RefreshDatabase trait.

When you include this trait in your test class, Laravel performs two steps:

  1. It migrates your database before the test suite runs (or on the first test).
  2. It wraps every individual test in a database transaction, rolling it back immediately after the test completes.

A Concrete Worked Example

Let’s test our TaskService in a real integration scenario. We want to verify that when a task is created, it is correctly associated with the project and that our business logic (like setting a default status) is applied.

PHP
namespace Tests\Integration;

use Tests\TestCase;
use App\Models\Project;
use App\Models\User;
use App\Services\TaskService;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TaskWorkflowTest extends TestCase
{
    use RefreshDatabase;

    public function test_task_creation_workflow_persists_correctly()
    {
        #6A9955">// 1. Setup: Use factories to prepare the environment
        $user = User::factory()->create();
        $project = Project::factory()->for($user)->create();
        $service = app(TaskService::class);

        #6A9955">// 2. Action: Execute the service method
        $task = $service->createTask($project, [
            'title' => 'Complete documentation',
            'description' => 'Write the API guide'
        ]);

        #6A9955">// 3. Assertion: Verify the database state directly
        $this->assertDatabaseHas('tasks', [
            'id' => $task->id,
            'project_id' => $project->id,
            'status' => 'pending' #6A9955">// Our default business logic
        ]);

        $this->assertEquals(1, $project->tasks()->count());
    }
}

By using assertDatabaseHas, we aren't just checking if the object exists in memory; we are querying the database to ensure the persistence layer captured the data exactly as intended.

Hands-on Exercise

Using the project board we’ve been building:

  1. Create a new test file: php artisan make:test Integration/ProjectAssignmentTest.
  2. Use the RefreshDatabase trait.
  3. Write a test that:
    • Creates a Project and two Users.
    • Uses your ProjectService to assign the second user to the project.
    • Asserts that the project_user pivot table contains the correct record.
  4. Run the test with php artisan test.

Common Pitfalls

Even senior engineers run into these issues when writing integration tests:

  • Over-mocking: If you find yourself using Mockery or Event::fake() inside an integration test, ask yourself if you're still testing the integration. Keep integration tests focused on the real code interaction. Reference Mocking Services and Repositories in Laravel Tests to understand when to shift from mocking to true integration.
  • Seeding Bloat: Avoid using large seeders in your setUp() method. Use Mastering Laravel Database Factories and Seeding for Testing to create minimal, specific data for the test at hand.
  • Ignoring Transactional Boundaries: If your service uses DB::transaction, make sure your test doesn't accidentally commit data that breaks subsequent tests. Laravel’s RefreshDatabase is generally smart enough, but complex manual transactions can sometimes interfere with the test runner's rollback mechanism.

Recap

Integration testing bridges the gap between isolated logic and the real-world behavior of your application. By leveraging the RefreshDatabase trait and focusing on database assertions, you ensure that your services, repositories, and database schema work together as a cohesive system. This level of testing is your best defense against regression in complex workflows.

Up next: We will dive into testing API authentication to ensure our protected endpoints remain secure.

Previous lessonDatabase Migrations Best PracticesNext lesson Testing API Authentication
Back to Blog

Similar Posts

LaravelJune 26, 20263 min read

Mastering Laravel Database Factories and Seeding for Testing

Learn to master Laravel database factories and seeders to generate realistic test data. Create state modifiers and automate your testing workflow efficiently.

Read more
LaravelJune 25, 20263 min read

Testing Forms and Validation in Laravel: A Practical Guide

Stop manually testing your forms. Learn how to use Laravel's testing suite to automate validation checks, simulate authenticated users, and ensure data integrity.

Part of the course

Intermediate Laravel: Real-World Application Patterns

intermediate · Lesson 38 of 58

  1. 1

    Architecting for Maintainability

    3 min
  2. 2

    Implementing the Service Layer

    3 min
  3. 3

    Repository Pattern Fundamentals

    3 min
Read more
LaravelJune 25, 20263 min read

Introduction to Database Relationships in Laravel

Learn how to define hasMany and belongsTo relationships in Laravel. Master Eloquent database relationships to link models and access related data with ease.

Read more
  • 4

    Project Board Domain Modeling

    3 min
  • 5

    Advanced Eloquent Scopes and Accessors

    4 min
  • 6

    Service-Oriented Task Management

    3 min
  • 7

    REST API Fundamentals with Sanctum

    3 min
  • 8

    Resource Controllers and API Responses

    3 min
  • 9

    Handling API Validation and Form Requests

    3 min
  • 10

    Implementing Middleware for API Security

    4 min
  • 11

    Database Transactions for Data Integrity

    3 min
  • 12

    Error Handling and Global Exceptions

    3 min
  • 13

    Introduction to Laravel Events and Listeners

    3 min
  • 14

    Asynchronous Processing with Queues

    4 min
  • 15

    Job Chaining and Batching

    3 min
  • 16

    Feature Testing Fundamentals

    4 min
  • 17

    Mocking Services and Repositories in Tests

    3 min
  • 18

    Testing Events and Jobs

    3 min
  • 19

    Database Factories and Seeding

    3 min
  • 20

    API Versioning Strategies

    4 min
  • 21

    Advanced Request Filtering and Sorting

    3 min
  • 22

    Handling File Uploads in REST APIs

    3 min
  • 23

    Real-time Notifications with Broadcasting

    3 min
  • 24

    Using Observers for Model Lifecycle Hooks

    3 min
  • 25

    Implementing Policies for Authorization

    3 min
  • 26

    Customizing Authentication Guards

    3 min
  • 27

    Rate Limiting API Endpoints

    4 min
  • 28

    Eloquent Performance Optimization

    4 min
  • 29

    Caching Strategies for Performance

    4 min
  • 30

    Using Traits for Code Reuse

    3 min
  • 31

    Advanced Dependency Injection with Service Providers

    3 min
  • 32

    Command Line Tools with Artisan

    3 min
  • 33

    Scheduled Tasks and Cron Jobs

    3 min
  • 34

    Integrating Third-Party Services

    3 min
  • 35

    Handling Webhooks

    3 min
  • 36

    Logging and Monitoring

    3 min
  • 37

    Database Migrations Best Practices

    3 min
  • 38

    Advanced Testing: Integration Tests

    4 min
  • 39

    Testing API Authentication

    4 min
  • 40

    Code Quality and Static Analysis

    3 min
  • 41

    Project Structure for Large Applications

    3 min
  • 42

    Environment and Configuration Management

    3 min
  • 43

    Deploying Laravel Applications

    4 min
  • 44

    Database Indexing Strategies

    4 min
  • 45

    Using Value Objects

    4 min
  • 46

    Strategy Pattern for Business Rules

    3 min
  • 47

    Advanced Queue Monitoring

    3 min
  • 48

    Building a Search API

    3 min
  • 49

    Handling Concurrency and Race Conditions

    4 min
  • 50

    API Documentation with OpenAPI

    3 min
  • 51

    Testing with Test Doubles

    3 min
  • 52

    Implementing Multi-Tenancy

    4 min
  • 53

    Refactoring Legacy Code

    4 min
  • 54

    Using Middleware for Feature Flags

    3 min
  • 55

    Building Reusable Packages

    4 min
  • 56

    Performance Profiling

    3 min
  • 57

    Secure API Design

    3 min
  • 58

    Event Sourcing Concepts

    4 min
  • View full course