> ## 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

> Cómo utilizar las funciones de mock y test doubles de Laravel para ejecutar cada prueba como una unidad independiente.

## Por qué utilizar mocks

Al escribir pruebas hay procesos que no queremos ejecutar de verdad: enviar correos, leer o escribir la caché, llamar a APIs externas...
Sustituirlos por «impostores que se hacen pasar por lo real» se llama mocking.

Ventajas de usar mocks:

* **Los tests son rápidos**: no se ejecutan servicios externos ni operaciones lentas, así que terminan enseguida.
* **Los tests son estables**: no dependen del estado de un servicio externo y siempre dan el mismo resultado.
* **El alcance del test queda claro**: verificas una única clase o método.

Laravel ofrece helpers listos para mockear eventos, jobs, facades, etc.
Por dentro se apoya en [Mockery](https://github.com/mockery/mockery), pero no necesitas una configuración compleja.

```mermaid theme={null}
graph LR
    A["Test"] --> B["Mock<br>(impostor)"]
    A --> C["Código real"]
    B --> D["No se ejecuta<br>(API externa, correo, etc.)"]
    C --> B
    style B fill:#f9a,stroke:#c66
    style D fill:#eee,stroke:#aaa
```

## Objetos mock

Para mockear un objeto que se inyecta a través del contenedor, registra la instancia mock en él.
Así, el contenedor usa el mock en lugar de crear el objeto real.

<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>

### Método `mock()`

El método `mock()` de la clase base de tests de Laravel hace lo mismo con menos código.

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

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

### Método `partialMock()`

Si solo quieres mockear algunos métodos, utiliza `partialMock()`. Los que no se mockeen se ejecutan con normalidad.

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

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

### Método `spy()`

Un spy es parecido a un mock, pero verifica la interacción **después** de ejecutar el código.
Mientras que en un mock defines de antemano «este método debería llamarse», con un spy compruebas a posteriori «¿se ha llamado este método?».

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

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

// ...ejecuta el código que estás probando...

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

```mermaid theme={null}
graph TD
    subgraph "Mock"
        M1["Definir expectativas previas<br>expects('process')"] --> M2["Ejecutar el código"] --> M3["Verificación automática"]
    end
    subgraph "Spy"
        S1["Configurar el spy"] --> S2["Ejecutar el código"] --> S3["Verificar después<br>shouldHaveReceived('process')"]
    end
```

## Mockear facades

A diferencia de las llamadas estáticas normales, los [facades](/es/facades) sí se pueden mockear.
Ofrecen la misma testabilidad que la inyección de dependencias con una sintaxis mucho más concisa.

Considera este controlador que utiliza la caché.

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

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Devuelve el listado de usuarios de la aplicación
     */
    public function index(): array
    {
        $value = Cache::get('key');

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

Para mockear `Cache::get`, usa `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>
  No mockees el facade `Request`. En su lugar, pasa los datos de entrada a los métodos HTTP de prueba (`get`, `post`, etc.). De igual manera, en lugar de mockear el facade `Config`, llama a `Config::set()` dentro del test.
</Warning>

## Spies de facade

Para monitorizar un facade con un spy, llama a su método `spy()`.
Es útil cuando quieres verificar la interacción tras ejecutar el código.

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

  use Illuminate\Support\Facades\Cache;

  test('los valores se guardan en la caché', 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>

## Manipulación del tiempo

Cuando pruebas lógica que depende del tiempo, resulta útil poder cambiar la hora que devuelven `now()` o `Carbon::now()`.
La clase base de los feature tests de Laravel incluye helpers para manipular el tiempo.

### `travel()`: viajar en el tiempo

<CodeGroup>
  ```php Pest theme={null}
  test('se puede manipular el tiempo', function () {
      // Avanzar
      $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();

      // Retroceder
      $this->travel(-5)->hours();

      // Ir a un instante concreto
      $this->travelTo(now()->subHours(6));

      // Volver a la hora actual
      $this->travelBack();
  });
  ```

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

      // Retroceder
      $this->travel(-5)->hours();

      // Ir a un instante concreto
      $this->travelTo(now()->subHours(6));

      // Volver a la hora actual
      $this->travelBack();
  }
  ```
</CodeGroup>

### Viaje temporal con closures

Si pasas una closure a los métodos de viaje, el tiempo queda congelado en el instante indicado durante su ejecución y, al terminar, vuelve al momento original.

```php theme={null}
$this->travel(5)->days(function () {
    // Probar el estado dentro de 5 días
});

$this->travelTo(now()->subDays(10), function () {
    // Probar en un instante concreto
});
```

### `freezeTime()`: detener el tiempo

`freezeTime()` fija la hora actual. `freezeSecond()` fija la hora al inicio del segundo actual.

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

// Congelar el tiempo, ejecutar la closure y reanudar
$this->freezeTime(function (Carbon $time) {
    // ...
});

// Congelar al inicio del segundo actual y ejecutar la closure
$this->freezeSecond(function (Carbon $time) {
    // ...
});
```

### Ejemplo práctico: bloquear hilos inactivos

Manipular el tiempo es útil, por ejemplo, para probar funcionalidades como el bloqueo de un hilo de foro tras un periodo de inactividad.

<CodeGroup>
  ```php Pest theme={null}
  use App\Models\Thread;

  test('los hilos del foro se bloquean tras una semana de inactividad', 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>
  Los métodos como `travel()` solo pueden usarse en feature tests (clases que heredan de `Tests\TestCase`). No están disponibles en el `TestCase` base de PHPUnit.
</Info>

## Lista de métodos

### Relacionados con mocks

| Método                                 | Descripción                                                      |
| -------------------------------------- | ---------------------------------------------------------------- |
| `$this->mock(Class::class, fn)`        | Crea un mock completo de la clase y lo registra en el contenedor |
| `$this->partialMock(Class::class, fn)` | Mockea solo algunos métodos                                      |
| `$this->spy(Class::class)`             | Crea un spy y lo registra en el contenedor                       |
| `$this->instance(Class::class, $mock)` | Registra una instancia mock arbitraria en el contenedor          |
| `Facade::expects('method')`            | Mockea un método de un facade                                    |
| `Facade::spy()`                        | Espía un facade                                                  |
| `$spy->shouldHaveReceived('method')`   | Verifica que el spy recibió una llamada                          |

### Manipulación del tiempo

| Método                     | Descripción                                                                   |
| -------------------------- | ----------------------------------------------------------------------------- |
| `$this->travel(n)->unit()` | Avanza el tiempo la cantidad indicada                                         |
| `$this->travelTo(Carbon)`  | Va a un instante concreto                                                     |
| `$this->travelBack()`      | Vuelve al momento actual                                                      |
| `$this->freezeTime(fn)`    | Congela el tiempo mientras se ejecuta la closure                              |
| `$this->freezeSecond(fn)`  | Congela el tiempo al inicio del segundo actual mientras se ejecuta la closure |


## Related topics

- [Actualización de Laravel 9 a 10](/es/blog/upgrade-9-to-10.md)
