Database testing — RefreshDatabase vs DatabaseTransactions
Concept
Mocking in Laravel tests replaces real implementations with fakes to isolate the code under test and verify interactions. Laravel provides Facade fakes for most built-in services — they're simpler and less error-prone than Mockery.
Facade fakes: Mail::fake(), Event::fake(), Queue::fake(), Storage::fake(), Notification::fake(), Bus::fake(), Http::fake(). Call at the start of the test. The real implementation is replaced. Fake assertions verify the right things were dispatched.
Mail::fake():
Mail::assertSent(WelcomeMail::class).Mail::assertSent(WelcomeMail::class, 2)— sent exactly twice.Mail::assertSent(WelcomeMail::class, fn($mail) => $mail->hasTo('alice@example.com')).Mail::assertNotSent(ResetEmail::class).Mail::assertNothingSent().Mail::assertQueued(WelcomeMail::class)— for queueable mailables.
Event::fake():
Event::assertDispatched(UserRegistered::class).Event::assertNotDispatched(UserBanned::class).Event::assertDispatchedTimes(OrderPlaced::class, 3).
Queue::fake():
Queue::assertPushed(SendWeeklyReport::class).Queue::assertPushedOn('emails', SendWeeklyReport::class).Queue::assertNothingPushed().
Storage::fake(): Creates an in-memory disk. File operations work but nothing touches the real filesystem.
Mockery: For mocking non-facade classes or when you need to assert specific method calls with specific arguments. Use Mockery::mock(MyService::class) and bind it in the container: $this->app->instance(MyService::class, $mock).
Code Example
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;
use Tests\TestCase;
class RegistrationTest extends TestCase
{
use RefreshDatabase;
public function test_registration_sends_welcome_email(): void
{
Mail::fake();
$this->postJson('/api/auth/register', [
'name' => 'Alice',
'email' => 'alice@example.com',
'password' => 'password',
'password_confirmation' => 'password',
])->assertCreated();
Mail::assertSent(\App\Mail\WelcomeMail::class, function($mail) {
return $mail->hasTo('alice@example.com');
});
}
public function test_registration_fires_user_registered_event(): void
{
Event::fake();
User::factory()->create(['email' => 'alice@example.com']);
Event::assertDispatched(\App\Events\UserRegistered::class, function($event) {
return $event->user->email === 'alice@example.com';
});
}
public function test_upload_stores_in_correct_disk(): void
{
Storage::fake('s3');
$user = User::factory()->create();
$file = UploadedFile::fake()->image('avatar.jpg', 200, 200);
$this->actingAs($user)
->postJson('/api/avatar', ['avatar' => $file])
->assertOk();
Storage::disk('s3')->assertExists('avatars/' . $user->id . '.jpg');
}
public function test_order_queues_confirmation_job(): void
{
Queue::fake();
$user = User::factory()->create();
$this->actingAs($user)->postJson('/api/orders', ['product_id' => 1]);
Queue::assertPushed(\App\Jobs\SendOrderConfirmation::class, function($job) use ($user) {
return $job->userId === $user->id;
});
Queue::assertPushedOn('emails', \App\Jobs\SendOrderConfirmation::class);
}
}