Alaa Amer Articles

We offer a comprehensive collection of essential educational articles in web development to turn your ideas into digital reality

Testing in Laravel: Comprehensive Testing Strategies & Best Practices

Laravel 2026-01-01 Alaa Amer

Testing in Laravel: Comprehensive Testing Strategies & Best Practices

Expert Guide by Alaa Amer – Professional Web Developer & Applications Designer

Laravel provides robust testing infrastructure with PHPUnit integration. Master comprehensive testing strategies, TDD practices, and enterprise-level quality assurance techniques.

2️⃣ Comprehensive Unit Testing

Model Unit Tests:

<?php
// tests/Unit/Models/PostTest.php
namespace Tests\Unit\Models;

use Tests\TestCase;
use App\Models\{Post, User, Category, Tag};
use Illuminate\Foundation\Testing\RefreshDatabase;
use Carbon\Carbon;

class PostTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        $this->user = User::factory()->create();
        $this->category = Category::factory()->create();
        $this->post = Post::factory()->create([
            'user_id' => $this->user->id,
            'category_id' => $this->category->id
        ]);
    }

    /** @test */
    public function it_belongs_to_a_user(): void
    {
        $this->assertInstanceOf(User::class, $this->post->user);
        $this->assertEquals($this->user->id, $this->post->user->id);
    }

    /** @test */
    public function it_belongs_to_a_category(): void
    {
        $this->assertInstanceOf(Category::class, $this->post->category);
        $this->assertEquals($this->category->id, $this->post->category->id);
    }

    /** @test */
    public function it_can_have_many_tags(): void
    {
        $tags = Tag::factory()->count(3)->create();
        $this->post->tags()->attach($tags->pluck('id'));

        $this->assertCount(3, $this->post->tags);
        $this->assertInstanceOf(Tag::class, $this->post->tags->first());
    }

    /** @test */
    public function it_can_check_if_published(): void
    {
        // Test published post
        $publishedPost = Post::factory()->published()->create();
        $this->assertTrue($publishedPost->isPublished());

        // Test draft post
        $draftPost = Post::factory()->draft()->create();
        $this->assertFalse($draftPost->isPublished());

        // Test scheduled post (future date)
        $scheduledPost = Post::factory()->create([
            'status' => 'scheduled',
            'published_at' => now()->addDay()
        ]);
        $this->assertFalse($scheduledPost->isPublished());
    }

    /** @test */
    public function it_generates_slug_automatically(): void
    {
        $post = Post::factory()->make([
            'title' => 'Laravel Testing Best Practices',
            'slug' => null
        ]);

        $post->save();

        $this->assertEquals('laravel-testing-best-practices', $post->slug);
    }

    /** @test */
    public function it_ensures_unique_slugs(): void
    {
        $firstPost = Post::factory()->create([
            'title' => 'Laravel Testing',
            'slug' => 'laravel-testing'
        ]);

        $secondPost = Post::factory()->create([
            'title' => 'Laravel Testing',
            'slug' => null
        ]);

        $this->assertEquals('laravel-testing', $firstPost->slug);
        $this->assertEquals('laravel-testing-1', $secondPost->slug);
    }

    /** @test */
    public function it_calculates_reading_time(): void
    {
        $shortContent = str_repeat('word ', 100); // ~100 words
        $longContent = str_repeat('word ', 600);  // ~600 words

        $shortPost = Post::factory()->create(['content' => $shortContent]);
        $longPost = Post::factory()->create(['content' => $longContent]);

        $this->assertEquals(1, $shortPost->reading_time); // Min 1 minute
        $this->assertEquals(3, $longPost->reading_time);  // ~3 minutes
    }

    /** @test */
    public function it_scopes_published_posts(): void
    {
        Post::factory()->published()->count(5)->create();
        Post::factory()->draft()->count(3)->create();
        Post::factory()->create([
            'status' => 'scheduled',
            'published_at' => now()->addDay()
        ]);

        $publishedPosts = Post::published()->get();

        $this->assertCount(5, $publishedPosts);
        $publishedPosts->each(function ($post) {
            $this->assertEquals('published', $post->status);
            $this->assertLessThanOrEqual(now(), $post->published_at);
        });
    }

    /** @test */
    public function it_scopes_featured_posts(): void
    {
        Post::factory()->featured()->count(3)->create();
        Post::factory()->count(5)->create(['is_featured' => false]);

        $featuredPosts = Post::featured()->get();

        $this->assertCount(3, $featuredPosts);
        $featuredPosts->each(function ($post) {
            $this->assertTrue($post->is_featured);
        });
    }

    /** @test */
    public function it_can_be_searched_by_content(): void
    {
        Post::factory()->create([
            'title' => 'Laravel Testing Guide',
            'content' => 'This is about PHP unit testing'
        ]);

        Post::factory()->create([
            'title' => 'Vue.js Components',
            'content' => 'This covers JavaScript testing'
        ]);

        $laravelPosts = Post::search('Laravel')->get();
        $testingPosts = Post::search('testing')->get();

        $this->assertCount(1, $laravelPosts);
        $this->assertCount(2, $testingPosts);
    }
}

Service Layer Unit Tests:

<?php
// tests/Unit/Services/PostServiceTest.php
namespace Tests\Unit\Services;

use Tests\TestCase;
use App\Services\PostService;
use App\Models\{Post, User, Category};
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

class PostServiceTest extends TestCase
{
    use RefreshDatabase;

    protected PostService $postService;
    protected User $user;
    protected Category $category;

    protected function setUp(): void
    {
        parent::setUp();

        $this->postService = app(PostService::class);
        $this->user = User::factory()->create();
        $this->category = Category::factory()->create();

        Storage::fake('public');
    }

    /** @test */
    public function it_creates_post_with_valid_data(): void
    {
        $postData = [
            'title' => 'Test Post Title',
            'content' => 'This is the post content that is long enough for validation.',
            'excerpt' => 'Short excerpt',
            'category_id' => $this->category->id,
            'status' => 'draft'
        ];

        $post = $this->postService->createPost($postData, $this->user);

        $this->assertInstanceOf(Post::class, $post);
        $this->assertEquals($postData['title'], $post->title);
        $this->assertEquals('test-post-title', $post->slug);
        $this->assertEquals($this->user->id, $post->user_id);
        $this->assertDatabaseHas('posts', [
            'title' => $postData['title'],
            'user_id' => $this->user->id
        ]);
    }

    /** @test */
    public function it_creates_post_with_image_upload(): void
    {
        $image = UploadedFile::fake()->image('test.jpg', 800, 600);

        $postData = [
            'title' => 'Post with Image',
            'content' => 'Content with uploaded image.',
            'category_id' => $this->category->id,
            'featured_image' => $image
        ];

        $post = $this->postService->createPost($postData, $this->user);

        $this->assertNotNull($post->featured_image);
        $this->assertStringContains('posts/', $post->featured_image);
        Storage::disk('public')->assertExists($post->featured_image);
    }

    /** @test */
    public function it_updates_post_data(): void
    {
        $post = Post::factory()->create([
            'user_id' => $this->user->id,
            'title' => 'Original Title',
            'content' => 'Original content'
        ]);

        $updateData = [
            'title' => 'Updated Title',
            'content' => 'Updated content with more information.'
        ];

        $updatedPost = $this->postService->updatePost($post, $updateData);

        $this->assertEquals('Updated Title', $updatedPost->title);
        $this->assertEquals('updated-title', $updatedPost->slug);
        $this->assertDatabaseHas('posts', [
            'id' => $post->id,
            'title' => 'Updated Title'
        ]);
    }

    /** @test */
    public function it_publishes_draft_post(): void
    {
        $post = Post::factory()->draft()->create(['user_id' => $this->user->id]);

        $this->assertNull($post->published_at);
        $this->assertEquals('draft', $post->status);

        $publishedPost = $this->postService->publishPost($post);

        $this->assertEquals('published', $publishedPost->status);
        $this->assertNotNull($publishedPost->published_at);
        $this->assertLessThanOrEqual(now(), $publishedPost->published_at);
    }

    /** @test */
    public function it_schedules_post_for_future_publication(): void
    {
        $post = Post::factory()->draft()->create(['user_id' => $this->user->id]);
        $futureDate = now()->addDays(3);

        $scheduledPost = $this->postService->schedulePost($post, $futureDate);

        $this->assertEquals('scheduled', $scheduledPost->status);
        $this->assertEquals($futureDate->format('Y-m-d H:i:s'), $scheduledPost->published_at->format('Y-m-d H:i:s'));
    }

    /** @test */
    public function it_deletes_post_with_dependencies(): void
    {
        $post = Post::factory()->create(['user_id' => $this->user->id]);

        // Add some comments and likes
        $post->comments()->create([
            'content' => 'Test comment',
            'user_id' => $this->user->id
        ]);

        $this->assertTrue($this->postService->deletePost($post));

        $this->assertModelSoftDeleted($post);
        $this->assertDatabaseMissing('comments', [
            'post_id' => $post->id,
            'deleted_at' => null
        ]);
    }

    /** @test */
    public function it_gets_related_posts(): void
    {
        $category = Category::factory()->create();
        $mainPost = Post::factory()->published()->create([
            'category_id' => $category->id
        ]);

        // Create related posts in same category
        $relatedPosts = Post::factory()->published()->count(5)->create([
            'category_id' => $category->id
        ]);

        // Create posts in different category
        Post::factory()->published()->count(3)->create([
            'category_id' => Category::factory()->create()->id
        ]);

        $result = $this->postService->getRelatedPosts($mainPost, 3);

        $this->assertCount(3, $result);
        $result->each(function ($post) use ($mainPost, $category) {
            $this->assertNotEquals($mainPost->id, $post->id);
            $this->assertEquals($category->id, $post->category_id);
            $this->assertEquals('published', $post->status);
        });
    }

    /** @test */
    public function it_searches_posts_by_query(): void
    {
        Post::factory()->published()->create([
            'title' => 'Laravel Testing Best Practices',
            'content' => 'Guide about PHP unit testing'
        ]);

        Post::factory()->published()->create([
            'title' => 'Vue.js Components',
            'content' => 'Frontend JavaScript development'
        ]);

        Post::factory()->published()->create([
            'title' => 'Laravel API Development',
            'content' => 'Building REST APIs with Laravel'
        ]);

        $laravelResults = $this->postService->searchPosts('Laravel');
        $testingResults = $this->postService->searchPosts('testing');
        $apiResults = $this->postService->searchPosts('API');

        $this->assertCount(2, $laravelResults);
        $this->assertCount(1, $testingResults);
        $this->assertCount(1, $apiResults);
    }
}

4️⃣ Advanced Testing Patterns

Mock & Stub Testing:

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

use Tests\TestCase;
use App\Models\{Post, User};
use App\Notifications\PostPublished;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\{Notification, Queue, Event};
use Illuminate\Notifications\AnonymousNotifiable;

class EmailNotificationTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function post_publication_sends_notification_to_subscribers(): void
    {
        Notification::fake();

        $author = User::factory()->create();
        $subscribers = User::factory()->count(3)->create();

        // Set up subscriptions
        foreach ($subscribers as $subscriber) {
            $author->subscribers()->attach($subscriber);
        }

        $post = Post::factory()->create(['user_id' => $author->id]);

        // Publish the post
        $post->update(['status' => 'published', 'published_at' => now()]);

        // Assert notification was sent
        Notification::assertSentTo(
            $subscribers,
            PostPublished::class,
            function ($notification, $channels) use ($post) {
                return $notification->post->id === $post->id;
            }
        );
    }

    /** @test */
    public function queued_job_processes_email_sending(): void
    {
        Queue::fake();

        $user = User::factory()->create();
        $post = Post::factory()->create();

        // Trigger job
        dispatch(new \App\Jobs\SendPostNotification($post, $user));

        Queue::assertPushed(\App\Jobs\SendPostNotification::class, function ($job) use ($post, $user) {
            return $job->post->id === $post->id &&
                   $job->user->id === $user->id;
        });
    }
}

// tests/Unit/Services/PaymentServiceTest.php - Testing External APIs
namespace Tests\Unit\Services;

use Tests\TestCase;
use App\Services\PaymentService;
use Illuminate\Support\Facades\Http;

class PaymentServiceTest extends TestCase
{
    /** @test */
    public function it_processes_payment_successfully(): void
    {
        Http::fake([
            'https://api.payment-provider.com/charges' => Http::response([
                'id' => 'charge_123',
                'status' => 'succeeded',
                'amount' => 2000
            ], 200)
        ]);

        $paymentService = new PaymentService();

        $result = $paymentService->processPayment([
            'amount' => 20.00,
            'currency' => 'USD',
            'token' => 'tok_visa'
        ]);

        $this->assertTrue($result['success']);
        $this->assertEquals('charge_123', $result['charge_id']);

        Http::assertSent(function ($request) {
            return $request->url() === 'https://api.payment-provider.com/charges' &&
                   $request['amount'] === 2000 &&
                   $request['currency'] === 'USD';
        });
    }

    /** @test */
    public function it_handles_payment_failure_gracefully(): void
    {
        Http::fake([
            'https://api.payment-provider.com/charges' => Http::response([
                'error' => ['message' => 'Card was declined']
            ], 402)
        ]);

        $paymentService = new PaymentService();

        $result = $paymentService->processPayment([
            'amount' => 20.00,
            'currency' => 'USD',
            'token' => 'tok_declined'
        ]);

        $this->assertFalse($result['success']);
        $this->assertStringContains('declined', $result['error']);
    }
}

Database Transaction Testing:

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

use Tests\Feature\DatabaseTestCase;
use App\Models\{User, Post, Comment};
use Illuminate\Support\Facades\DB;

class DatabaseTransactionTest extends DatabaseTestCase
{
    /** @test */
    public function it_rolls_back_transaction_on_failure(): void
    {
        $user = User::factory()->create();
        $post = Post::factory()->create();

        $initialCommentCount = Comment::count();

        try {
            DB::transaction(function () use ($user, $post) {
                // Create a comment
                Comment::create([
                    'user_id' => $user->id,
                    'post_id' => $post->id,
                    'content' => 'Test comment'
                ]);

                // Simulate an error that causes rollback
                throw new \Exception('Simulated error');
            });
        } catch (\Exception $e) {
            // Expected exception
        }

        // Assert no comment was created due to rollback
        $this->assertEquals($initialCommentCount, Comment::count());
        $this->assertDatabaseMissing('comments', [
            'content' => 'Test comment'
        ]);
    }

    /** @test */
    public function it_commits_transaction_on_success(): void
    {
        $user = User::factory()->create();
        $post = Post::factory()->create();

        DB::transaction(function () use ($user, $post) {
            Comment::create([
                'user_id' => $user->id,
                'post_id' => $post->id,
                'content' => 'Successful comment'
            ]);

            $post->increment('comments_count');
        });

        $this->assertDatabaseHas('comments', [
            'content' => 'Successful comment'
        ]);

        $this->assertDatabaseHas('posts', [
            'id' => $post->id,
            'comments_count' => 1
        ]);
    }
}

Next Steps

Optimize your Laravel applications for peak performance with Laravel Performance Optimization techniques and monitoring.

📩 Need help with Laravel testing?

Laravel Testing PHPUnit Feature Tests Unit Tests Database Testing API Testing TDD
Article Category
Laravel

Testing in Laravel: Comprehensive Testing Strategies & Best Practices

Master Laravel testing with PHPUnit, Feature tests, Unit tests, Database testing, API testing, and advanced testing patterns for robust applications.

Testing in Laravel: Comprehensive Testing Strategies & Best Practices
01

Consultation & Communication

Direct communication via WhatsApp or phone to understand your project needs precisely.

02

Planning & Scheduling

Creating clear work plan with specific timeline for each project phase.

03

Development & Coding

Building projects with latest technologies ensuring high performance and security.

04

Testing & Delivery

Comprehensive testing and thorough review before final project delivery.

Alaa Amer
Alaa Amer

Professional web developer with over 10 years of experience in building innovative digital solutions.

Need This Service?

Contact me now for a free consultation and quote

WhatsApp Your satisfaction is our ultimate goal

What We Offer

  • Website Maintenance & Updates

    Keep your website secure updated optimized

  • API Integration

    Connect your systems with powerful APIs

  • Database Design & Optimization

    Faster queries cleaner structure fewer issues

  • Website Security Hardening

    Protect your site from cyber threats

  • Automation & Scripts

    Automate repetitive tasks and save time

Have Questions?

Call Us Now

00201014714795