Zum Hauptinhalt springen

Wozu Mocks

Beim Schreiben von Tests gibt es Vorgänge, die Sie nicht tatsächlich ausführen möchten – etwa den Versand von E-Mails, das Lesen und Schreiben von Caches oder Aufrufe an externe APIs. Solche Vorgänge durch eine „Fälschung, die sich wie das Original verhält“ zu ersetzen, nennt man Mocking. Die Vorteile von Mocks:
  • Schnelle Tests – Es werden keine externen Dienste oder aufwendigen Operationen ausgeführt, daher laufen die Tests schnell ab.
  • Stabile Tests – Die Tests hängen nicht vom Zustand externer Dienste ab und liefern stets dasselbe Ergebnis.
  • Klarer Testfokus – Es lässt sich gezielt eine einzelne Klasse oder Methode prüfen.
Laravel bietet direkt einsetzbare Helfer für das Mocken von Events, Jobs, Fassaden usw. Intern verwendet es Mockery, sodass Sie ohne komplexes Setup loslegen können.

Mock-Objekte

Um ein Objekt zu mocken, das über den Service-Container injiziert wird, binden Sie die Mock-Instanz in den Container ein. Der Container verwendet dann statt einer neu erzeugten Instanz Ihre Mock-Instanz.
use App\Service;
use Mockery;
use Mockery\MockInterface;

test('something can be mocked', function () {
    $this->instance(
        Service::class,
        Mockery::mock(Service::class, function (MockInterface $mock) {
            $mock->expects('process');
        })
    );
});
use App\Service;
use Mockery;
use Mockery\MockInterface;

public function test_something_can_be_mocked(): void
{
    $this->instance(
        Service::class,
        Mockery::mock(Service::class, function (MockInterface $mock) {
            $mock->expects('process');
        })
    );
}

Die Methode mock()

Mit der Methode mock(), die die TestCase-Basisklasse von Laravel bereitstellt, schreiben Sie dasselbe kürzer.
use App\Service;
use Mockery\MockInterface;

$mock = $this->mock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});

Die Methode partialMock()

Wenn Sie nur einen Teil der Methoden eines Objekts mocken möchten, verwenden Sie partialMock(). Nicht gemockte Methoden werden wie üblich ausgeführt.
use App\Service;
use Mockery\MockInterface;

$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});

Die Methode spy()

Ein Spy ist einem Mock ähnlich, unterscheidet sich aber darin, dass die Interaktion nach der Codeausführung überprüft wird. Während ein Mock vorab festlegt „diese Methode wird aufgerufen werden“, prüft ein Spy im Nachhinein „wurde diese Methode aufgerufen“.
use App\Service;

$spy = $this->spy(Service::class);

// ...den zu testenden Code ausführen...

$spy->shouldHaveReceived('process');

Fassaden mocken

Fassaden unterscheiden sich von gewöhnlichen statischen Methodenaufrufen dadurch, dass sie sich mocken lassen. Sie bieten die Testbarkeit von Dependency Injection bei gleichzeitig knapper Syntax. Als Beispiel ein Controller, der den Cache verwendet:
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Gibt die Liste aller Benutzer der Anwendung zurück.
     */
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
Um die Methode get der Cache-Fassade zu mocken, verwenden Sie expects().
<?php

use Illuminate\Support\Facades\Cache;

test('get index', function () {
    Cache::expects('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/users');

    // ...
});
<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class UserControllerTest extends TestCase
{
    public function test_get_index(): void
    {
        Cache::expects('get')
            ->with('key')
            ->andReturn('value');

        $response = $this->get('/users');

        // ...
    }
}
Mocken Sie nicht die Request-Fassade. Übergeben Sie stattdessen Eingabewerte an HTTP-Testmethoden wie get oder post. Rufen Sie ebenso statt die Config-Fassade zu mocken in Ihrem Test Config::set() auf.

Fassaden als Spy

Um eine Fassade als Spy zu beobachten, rufen Sie die Methode spy() der entsprechenden Fassade auf. Ein Spy ist praktisch, wenn Sie Interaktionen nach der Codeausführung überprüfen möchten.
<?php

use Illuminate\Support\Facades\Cache;

test('values are stored in cache', function () {
    Cache::spy();

    $response = $this->get('/');

    $response->assertStatus(200);

    Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
});
use Illuminate\Support\Facades\Cache;

public function test_values_are_stored_in_cache(): void
{
    Cache::spy();

    $response = $this->get('/');

    $response->assertStatus(200);

    Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
}

Zeit manipulieren

Um zeitabhängige Logik in Tests zu prüfen, ist es praktisch, den Zeitpunkt zu ändern, den now() oder Carbon::now() zurückgeben. Die Basisklasse für Feature-Tests von Laravel bietet dafür Helfer.

travel() – Zeit verschieben

test('time can be manipulated', function () {
    // In die Zukunft
    $this->travel(5)->milliseconds();
    $this->travel(5)->seconds();
    $this->travel(5)->minutes();
    $this->travel(5)->hours();
    $this->travel(5)->days();
    $this->travel(5)->weeks();
    $this->travel(5)->years();

    // In die Vergangenheit
    $this->travel(-5)->hours();

    // Zu einem bestimmten Zeitpunkt springen
    $this->travelTo(now()->subHours(6));

    // Zur aktuellen Zeit zurückkehren
    $this->travelBack();
});
public function test_time_can_be_manipulated(): void
{
    // In die Zukunft
    $this->travel(5)->milliseconds();
    $this->travel(5)->seconds();
    $this->travel(5)->minutes();
    $this->travel(5)->hours();
    $this->travel(5)->days();
    $this->travel(5)->weeks();
    $this->travel(5)->years();

    // In die Vergangenheit
    $this->travel(-5)->hours();

    // Zu einem bestimmten Zeitpunkt springen
    $this->travelTo(now()->subHours(6));

    // Zur aktuellen Zeit zurückkehren
    $this->travelBack();
}

Zeitmanipulation mit Closure

Wenn Sie den Reise-Methoden eine Closure übergeben, wird die Zeit für den angegebenen Zeitpunkt angehalten, die Closure ausgeführt und danach wieder zur ursprünglichen Zeit zurückgesprungen.
$this->travel(5)->days(function () {
    // Test im Zustand nach 5 Tagen
});

$this->travelTo(now()->subDays(10), function () {
    // Test zu einem bestimmten Zeitpunkt
});

freezeTime() – Zeit einfrieren

freezeTime() friert die aktuelle Zeit ein. freezeSecond() fixiert die Zeit am Anfang der aktuellen Sekunde.
use Illuminate\Support\Carbon;

// Zeit einfrieren, Closure ausführen und danach wieder freigeben
$this->freezeTime(function (Carbon $time) {
    // ...
});

// An den Anfang der aktuellen Sekunde einfrieren und Closure ausführen
$this->freezeSecond(function (Carbon $time) {
    // ...
});

Praxisbeispiel: Sperren inaktiver Threads

Zeitmanipulation ist beispielsweise hilfreich, um Funktionen wie das Sperren von Beiträgen in einem Forum nach längerer Inaktivität zu testen.
use App\Models\Thread;

test('forum threads lock after one week of inactivity', function () {
    $thread = Thread::factory()->create();

    $this->travel(1)->week();

    expect($thread->isLockedByInactivity())->toBeTrue();
});
use App\Models\Thread;

public function test_forum_threads_lock_after_one_week_of_inactivity(): void
{
    $thread = Thread::factory()->create();

    $this->travel(1)->week();

    $this->assertTrue($thread->isLockedByInactivity());
}
Zeitmanipulationsmethoden wie travel() sind nur in Feature-Tests verfügbar (Klassen, die von Tests\TestCase erben). In der Basisklasse TestCase von PHPUnit können sie nicht verwendet werden.

Methodenübersicht

Mocks

MethodeBeschreibung
$this->mock(Class::class, fn)Erzeugt einen vollständigen Mock einer Klasse und registriert ihn im Container
$this->partialMock(Class::class, fn)Mockt nur einen Teil der Methoden
$this->spy(Class::class)Erzeugt einen Spy und registriert ihn im Container
$this->instance(Class::class, $mock)Registriert eine beliebige Mock-Instanz im Container
Facade::expects('method')Mockt eine Methode einer Fassade
Facade::spy()Beobachtet eine Fassade als Spy
$spy->shouldHaveReceived('method')Prüft bei einem Spy, ob eine Methode aufgerufen wurde

Zeitmanipulation

MethodeBeschreibung
$this->travel(n)->unit()Verschiebt die Zeit um die angegebene Einheit
$this->travelTo(Carbon)Springt zu einem bestimmten Zeitpunkt
$this->travelBack()Kehrt zur aktuellen Zeit zurück
$this->freezeTime(fn)Friert die Zeit ein und führt eine Closure aus
$this->freezeSecond(fn)Friert die Zeit am Sekundenanfang ein und führt eine Closure aus
Zuletzt geändert am 13. Juli 2026