Lessons → Laravel Advanced → Lesson 4

Testing in Laravel

Laravel
⏱ 28 min read🔥 AdvancedNot completed

Testing ensures your code works correctly and prevents bugs from reappearing. Laravel has excellent built-in testing support with PHPUnit and Pest.

Types of Tests

TypeTestsSpeed
UnitSingle class/method in isolationVery fast
FeatureFull HTTP request/response cycleMedium
Browser (Dusk)Real browser interactionSlow

Running Tests

Terminal
# Run all tests
php artisan test

# Run specific test file
php artisan test tests/Feature/PostTest.php

# Run specific test method
php artisan test --filter=test_user_can_create_post

# Run with coverage
php artisan test --coverage

Feature Test — API Endpoint

tests/Feature/PostTest.php
<?php
namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;  // Fresh DB for each test

    public function test_user_can_get_all_posts(): void
    {
        Post::factory(5)->create();

        $response = $this->getJson('/api/posts');

        $response
            ->assertStatus(200)
            ->assertJsonCount(5, 'data')
            ->assertJsonStructure([
                'data' => [['id', 'title', 'body', 'author']]
            ]);
    }

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

        $response = $this->actingAs($user, 'sanctum')
            ->postJson('/api/posts', [
                'title' => 'My Test Post',
                'body'  => 'This is the post body content.',
            ]);

        $response->assertStatus(201);
        $this->assertDatabaseHas('posts', [
            'title'   => 'My Test Post',
            'user_id' => $user->id,
        ]);
    }

    public function test_unauthenticated_user_cannot_create_post(): void
    {
        $response = $this->postJson('/api/posts', [
            'title' => 'Test',
            'body'  => 'Body',
        ]);

        $response->assertStatus(401);
    }

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

        $response = $this->actingAs($user, 'sanctum')
            ->postJson('/api/posts', ['body' => 'No title']);

        $response
            ->assertStatus(422)
            ->assertJsonValidationErrors(['title']);
    }
}

Unit Test

tests/Unit/PostTest.php
<?php
class PostTest extends TestCase
{
    public function test_post_can_generate_excerpt(): void
    {
        $post = new Post(['body' => 'This is a very long post body...']);
        $this->assertEquals('This is a very long...', $post->excerpt(20));
    }

    public function test_post_slug_is_generated_from_title(): void
    {
        $post = Post::factory()->make(['title' => 'Hello World Post']);
        $this->assertEquals('hello-world-post', $post->slug);
    }
}

Factories

database/factories/PostFactory.php
<?php
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id'    => User::factory(),
            'title'      => fake()->sentence(),
            'slug'       => fake()->slug(),
            'body'       => fake()->paragraphs(3, true),
            'published'  => fake()->boolean(70),  // 70% chance true
            'views'      => fake()->numberBetween(0, 10000),
        ];
    }

    // State modifier
    public function published(): static
    {
        return $this->state(['published' => true]);
    }
}

// Usage in tests
Post::factory(10)->create();
Post::factory()->published()->create();
Post::factory(5)->for($user)->create();
💡
Write tests as you code! At your company (ms-cms), tests would catch bugs before they reach production. Even basic feature tests for your main API endpoints can save hours of debugging.
← Previous Lesson Next Lesson →
🧠

Test your knowledge!

Take the Laravel quiz.

Take Quiz →