1. Pruebas
  2. Simulación

Introducción

Al probar aplicaciones Laravel, es posible que desees "simular" ciertos aspectos de tu aplicación para que no se ejecuten realmente durante una prueba dada. Por ejemplo, al probar un controlador que despacha un evento, es posible que desees simular los escuchadores de eventos para que no se ejecuten realmente durante la prueba. Esto te permite probar solo la respuesta HTTP del controlador sin preocuparte por la ejecución de los escuchadores de eventos, ya que los escuchadores de eventos se pueden probar en su propio caso de prueba.

Laravel proporciona métodos útiles para simular eventos, trabajos y otros facades de forma predeterminada. Estos helpers proporcionan principalmente una capa de conveniencia sobre Mockery para que no tengas que realizar llamadas complicadas a los métodos de Mockery manualmente.

Simulación de Objetos

Cuando simulas un objeto que va a ser inyectado en tu aplicación a través del contenedor de servicios de Laravel, deberás vincular tu instancia simulada en el contenedor como una vinculación instance. Esto instruirá al contenedor para que use tu instancia simulada del objeto en lugar de construir el objeto en sí:

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->shouldReceive('process')->once();
})
);
}

Para hacer esto más conveniente, puedes usar el método mock proporcionado por la clase de prueba base de Laravel. Por ejemplo, el siguiente ejemplo es equivalente al ejemplo anterior:

use App\Service;
use Mockery\MockInterface;
 
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});

Puedes usar el método partialMock cuando solo necesitas simular algunos métodos de un objeto. Los métodos que no se simulan se ejecutarán normalmente cuando se llamen:

use App\Service;
use Mockery\MockInterface;
 
$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});

De manera similar, si deseas espiar en un objeto, la clase de prueba base de Laravel ofrece un método spy como un envoltorio conveniente alrededor del método Mockery::spy. Los spies son similares a los mocks; sin embargo, los spies registran cualquier interacción entre el spy y el código que se está probando, lo que te permite hacer afirmaciones después de que se ejecuta el código:

use App\Service;
 
$spy = $this->spy(Service::class);
 
// ...
 
$spy->shouldHaveReceived('process');

Simulación de Facades

A diferencia de las llamadas tradicionales a métodos estáticos, los facades (incluidos los facades en tiempo real) se pueden simular. Esto proporciona una gran ventaja sobre los métodos estáticos tradicionales y te brinda la misma capacidad de prueba que tendrías si estuvieras utilizando la inyección de dependencias tradicional. Al realizar pruebas, a menudo querrás simular una llamada a un facade de Laravel que ocurre en uno de tus controladores. Por ejemplo, considera la siguiente acción del controlador:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\Cache;
 
class UserController extends Controller
{
/**
* Obtener una lista de todos los usuarios de la aplicación.
*/
public function index(): array
{
$value = Cache::get('key');
 
return [
// ...
];
}
}

Podemos simular la llamada al facade Cache usando el método shouldReceive, que devolverá una instancia de un Mockery mock. Dado que los facades realmente se resuelven y gestionan mediante el contenedor de servicios de Laravel, tienen mucha más capacidad de prueba que una clase estática típica. Por ejemplo, simulemos nuestra llamada al método get del facade Cache:

<?php
 
namespace Tests\Feature;
 
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
 
class UserControllerTest extends TestCase
{
public function test_get_index(): void
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
 
$response = $this->get('/users');
 
// ...
}
}

Advertencia No debes simular el facade Request. En su lugar, pasa la entrada que deseas en los métodos de prueba HTTP como get y post al ejecutar tu prueba. De manera similar, en lugar de simular el facade Config, llama al método Config::set en tus pruebas.

Espías de Facades

Si deseas espiar en un facade, puedes llamar al método spy en el facade correspondiente. Los spies son similares a los mocks; sin embargo, los spies registran cualquier interacción entre el spy y el código que se está probando, lo que te permite hacer afirmaciones después de que se ejecuta el código:

use Illuminate\Support\Facades\Cache;
 
public function test_values_are_be_stored_in_cache(): void
{
Cache::spy();
 
$response = $this->get('/');
 
$response->assertStatus(200);
 
Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
}

Interactuar con el Tiempo

Al realizar pruebas, es posible que ocasionalmente necesites modificar la hora devuelta por helpers como now o Illuminate\Support\Carbon::now(). Afortunadamente, la clase de prueba base de Laravel incluye helpers que te permiten manipular la hora actual:

use Illuminate\Support\Carbon;
 
public function test_time_can_be_manipulated(): void
{
// Viajar al futuro...
$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();
 
// Viajar al pasado...
$this->travel(-5)->hours();
 
// Viajar a un momento específico...
$this->travelTo(now()->subHours(6));
 
// Volver al tiempo presente...
$this->travelBack();
}

También puedes proporcionar un cierre (closure) a los diversos métodos de viaje en el tiempo. El cierre se invocará con el tiempo congelado en el momento especificado. Una vez que se ha ejecutado el cierre, el tiempo volverá a la normalidad:

$this->travel(5)->days(function () {
// Probar algo cinco días en el futuro...
});
 
$this->travelTo(now()->subDays(10), function () {
// Probar algo durante un momento dado...
});

El método freezeTime se puede usar para congelar la hora actual. De manera similar, el método freezeSecond congelará la hora actual pero al comienzo del segundo actual:

use Illuminate\Support\Carbon;
 
// Congelar el tiempo y reanudar el tiempo normal después de ejecutar el cierre...
$this->freezeTime(function (Carbon $time) {
// ...
});
 
// Congelar el tiempo en el segundo actual y reanudar el tiempo normal después de ejecutar el cierre...
$this->freezeSecond(function (Carbon $time) {
// ...
})

Como cabría esperar, todos los métodos discutidos anteriormente son principalmente útiles para probar el comportamiento de la aplicación sensible al tiempo, como bloquear publicaciones inactivas en un foro de discusión:

use App\Models\Thread;
 
public function test_forum_threads_lock_after_one_week_of_inactivity()
{
$thread = Thread::factory()->create();
 
$this->travel(1)->week();
 
$this->assertTrue($thread->isLockedByInactivity());
}