> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Mocking

> Hoe je met de mocking- en testdouble-functionaliteit van Laravel tests in geïsoleerde eenheden uitvoert

## Waarom mocken

Bij het schrijven van tests zijn er handelingen die je niet echt wilt uitvoeren, zoals e-mail versturen, de cache lezen en schrijven of externe API's aanroepen.
Het vervangen van zulke handelingen door "een nepobject dat zich als het echte voordoet" heet mocken.

De voordelen van mocken zijn als volgt:

* **Tests zijn snel** — er worden geen externe services of zware processen uitgevoerd, dus tests zijn zo klaar
* **Tests zijn stabiel** — ze zijn niet afhankelijk van de toestand van externe services en geven altijd hetzelfde resultaat
* **De testscope is duidelijk** — je kunt precies één klasse of methode verifiëren

Laravel biedt kant-en-klare helpers om events, jobs, facades en meer te mocken.
Intern wordt [Mockery](https://github.com/mockery/mockery) gebruikt, zonder dat complexe setup nodig is.

```mermaid theme={null}
graph LR
    A["Test"] --> B["Mock<br>(nepobject)"]
    A --> C["Productiecode"]
    B --> D["Wordt niet uitgevoerd<br>(externe API's, e-mail enz.)"]
    C --> B
    style B fill:#f9a,stroke:#c66
    style D fill:#eee,stroke:#aaa
```

## Mockobjecten

Om objecten te mocken die via de servicecontainer worden geïnjecteerd, bind je een mockinstantie aan de container.
De container gebruikt dan de mockinstantie in plaats van het object zelf aan te maken.

<CodeGroup>
  ```php Pest theme={null}
  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');
          })
      );
  });
  ```

  ```php PHPUnit theme={null}
  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');
          })
      );
  }
  ```
</CodeGroup>

### De `mock()`-methode

Met de `mock()`-methode van de basistestklasse van Laravel schrijf je hetzelfde als hierboven beknopter.

```php theme={null}
use App\Service;
use Mockery\MockInterface;

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

### De `partialMock()`-methode

Wil je slechts een deel van de methoden van een object mocken, gebruik dan `partialMock()`.
Niet-gemockte methoden worden gewoon uitgevoerd.

```php theme={null}
use App\Service;
use Mockery\MockInterface;

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

### De `spy()`-methode

Een spy lijkt op een mock, maar verschilt doordat je de interacties verifieert ná het uitvoeren van de code.
Waar een mock vooraf vastlegt "deze methode zou moeten worden aangeroepen", controleert een spy achteraf "of deze methode is aangeroepen".

```php theme={null}
use App\Service;

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

// ...de te testen code uitvoeren...

$spy->shouldHaveReceived('process');
```

```mermaid theme={null}
graph TD
    subgraph "Mock"
        M1["Vooraf verwachtingen instellen<br>expects('process')"] --> M2["Code uitvoeren"] --> M3["Automatische verificatie"]
    end
    subgraph "Spy"
        S1["Spy instellen"] --> S2["Code uitvoeren"] --> S3["Achteraf verifiëren<br>shouldHaveReceived('process')"]
    end
```

## Facades mocken

Anders dan gewone statische methodeaanroepen kunnen [facades](/nl/facades) worden gemockt.
Je krijgt dezelfde testbaarheid als bij dependency injection, maar met een beknoptere syntaxis.

Neem als voorbeeld een controller die de cache gebruikt.

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Geef een lijst van alle gebruikers van de applicatie terug
     */
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
```

Om de `get`-methode van de `Cache`-facade te mocken gebruik je `expects()`.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use Illuminate\Support\Facades\Cache;

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

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

      // ...
  });
  ```

  ```php PHPUnit theme={null}
  <?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');

          // ...
      }
  }
  ```
</CodeGroup>

<Warning>
  Mock de `Request`-facade niet. Geef in plaats daarvan de invoerwaarden door aan HTTP-testmethoden zoals `get` en `post`. Roep evenzo, in plaats van de `Config`-facade te mocken, `Config::set()` aan binnen je test.
</Warning>

## Facade-spies

Om een facade met een spy te observeren roep je de `spy()`-methode van de betreffende facade aan.
Spies zijn handig wanneer je interacties wilt verifiëren nadat de code is uitgevoerd.

<CodeGroup>
  ```php Pest theme={null}
  <?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);
  });
  ```

  ```php PHPUnit theme={null}
  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);
  }
  ```
</CodeGroup>

## De tijd manipuleren

Wanneer je in tests tijdsafhankelijke logica verifieert, is het handig om de tijd die `now()` of `Carbon::now()` teruggeeft te kunnen wijzigen.
De basisklasse voor feature-tests van Laravel biedt helpers voor het manipuleren van tijd.

### `travel()` — door de tijd reizen

<CodeGroup>
  ```php Pest theme={null}
  test('time can be manipulated', function () {
      // Naar de toekomst reizen
      $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();

      // Terug naar het verleden
      $this->travel(-5)->hours();

      // Naar een specifiek tijdstip reizen
      $this->travelTo(now()->subHours(6));

      // Terug naar de huidige tijd
      $this->travelBack();
  });
  ```

  ```php PHPUnit theme={null}
  public function test_time_can_be_manipulated(): void
  {
      // Naar de toekomst reizen
      $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();

      // Terug naar het verleden
      $this->travel(-5)->hours();

      // Naar een specifiek tijdstip reizen
      $this->travelTo(now()->subHours(6));

      // Terug naar de huidige tijd
      $this->travelBack();
  }
  ```
</CodeGroup>

### Tijdreizen met een closure

Geef je een closure door aan een tijdreismethode, dan wordt de tijd op het opgegeven tijdstip stilgezet, de closure uitgevoerd en daarna teruggekeerd naar de oorspronkelijke tijd.

```php theme={null}
$this->travel(5)->days(function () {
    // Testen in de toestand van over 5 dagen
});

$this->travelTo(now()->subDays(10), function () {
    // Testen op een specifiek moment
});
```

### `freezeTime()` — de tijd stilzetten

`freezeTime()` legt de huidige tijd vast. `freezeSecond()` legt de huidige tijd vast op het begin van de seconde.

```php theme={null}
use Illuminate\Support\Carbon;

// De tijd vastzetten, de closure uitvoeren en daarna weer hervatten
$this->freezeTime(function (Carbon $time) {
    // ...
});

// Vastzetten op het begin van de huidige seconde en de closure uitvoeren
$this->freezeSecond(function (Carbon $time) {
    // ...
});
```

### Praktijkvoorbeeld: inactieve threads vergrendelen

Tijdmanipulatie is nuttig voor het testen van functionaliteit zoals een forum waar posts worden vergrendeld na een periode van inactiviteit.

<CodeGroup>
  ```php Pest theme={null}
  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();
  });
  ```

  ```php PHPUnit theme={null}
  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());
  }
  ```
</CodeGroup>

<Info>
  Tijdmanipulatiemethoden zoals `travel()` zijn alleen te gebruiken in feature-tests (klassen die overerven van `Tests\TestCase`). In de basistestklasse `TestCase` van PHPUnit werken ze niet.
</Info>

## Methodenoverzicht

### Mocking

| Methode                                | Beschrijving                                                              |
| -------------------------------------- | ------------------------------------------------------------------------- |
| `$this->mock(Class::class, fn)`        | Maakt een volledige mock van de klasse en registreert die in de container |
| `$this->partialMock(Class::class, fn)` | Mockt slechts een deel van de methoden                                    |
| `$this->spy(Class::class)`             | Maakt een spy en registreert die in de container                          |
| `$this->instance(Class::class, $mock)` | Registreert een willekeurige mockinstantie in de container                |
| `Facade::expects('method')`            | Mockt een methode van de facade                                           |
| `Facade::spy()`                        | Observeert de facade met een spy                                          |
| `$spy->shouldHaveReceived('method')`   | Verifieert of de methode op de spy is aangeroepen                         |

### Tijdmanipulatie

| Methode                    | Beschrijving                                                          |
| -------------------------- | --------------------------------------------------------------------- |
| `$this->travel(n)->unit()` | Reist de opgegeven eenheid door de tijd                               |
| `$this->travelTo(Carbon)`  | Reist naar een specifiek tijdstip                                     |
| `$this->travelBack()`      | Keert terug naar de huidige tijd                                      |
| `$this->freezeTime(fn)`    | Zet de tijd vast en voert een closure uit                             |
| `$this->freezeSecond(fn)`  | Zet de tijd vast op het begin van de seconde en voert een closure uit |
