Mocks and Fakes

Mocking, Faking, and Travelling Through Time

Checkout calls an exchange-rate API, queues a SendInvoice job and emails a receipt, and a test must really do none of them. Each facade's fake() puts a recorder with assertions in the container in place of the real service, so the test checks what the code meant to send:

tests/Feature/CheckoutTest.php: faking HTTP, the queue and mailPHP
Http::preventStrayRequests();      // any URL that is not faked throws
Http::fake(['rates.example.com/*' => Http::response(['rates' => ['MYR' => 4.2]])]);
Queue::fake();
Mail::fake();
$user = User::factory()->create();
$book = Product::factory()->create(['price' => 10, 'stock' => 5]);
$this->actingAs($user)
    ->postJson('/checkout', ['product_id' => $book->id, 'qty' => 2])
    ->assertOk()
    ->assertExactJson(['total_usd' => 20, 'total_myr' => 84]);
Http::assertSent(fn ($request) => $request['base'] === 'USD');
Queue::assertPushed(SendInvoice::class, fn ($job) => $job->total === 20.0);
Mail::assertSent(ReceiptMail::class, fn ($mail) => $mail->hasTo($user->email));
$this->assertSame(3, $book->fresh()->stock);
Output
   PASS  Tests\Feature\CheckoutTest
  ✓ checkout converts queues and mails                                   0.17s
  ✓ a product stops being new after a week                               0.01s

The second test calls $this->travel(8)->days() between two isNew() checks. travelTo() and freezeTime() also move the clock that now() reads, and the clock resets after each test. Event, Notification and Storage have fakes too. For your own classes, $this->mock() binds a Mockery 10,724 double into the container (Mockery and Hand-Written Fakes).