Laravel ofrece una API muy completa para simular peticiones HTTP y verificar sus respuestas.
Puedes reproducir las peticiones dentro de los tests sin necesidad de levantar un servidor HTTP real.
<?phptest('la aplicación devuelve una respuesta correcta', function () { $response = $this->get('/'); $response->assertStatus(200);});
<?phpnamespace Tests\Feature;use Tests\TestCase;class ExampleTest extends TestCase{ public function test_the_application_returns_a_successful_response(): void { $response = $this->get('/'); $response->assertStatus(200); }}
El método get() envía una petición GET a la aplicación y assertStatus() valida el código HTTP de la respuesta.
Durante las pruebas el middleware CSRF se desactiva automáticamente. No hace falta desactivarlo de forma explícita.
Dentro de los tests puedes enviar peticiones con los métodos get, post, put, patch y delete.
Estos métodos no realizan una petición real por la red: simulan la petición internamente en la aplicación.
El valor devuelto es una instancia de Illuminate\Testing\TestResponse, que expone muchos métodos de aserción.
<?phptest('petición básica', function () { $response = $this->get('/'); $response->assertStatus(200);});
<?phpnamespace Tests\Feature;use Tests\TestCase;class ExampleTest extends TestCase{ public function test_a_basic_request(): void { $response = $this->get('/'); $response->assertStatus(200); }}
Se recomienda enviar una única petición por cada método de test. Ejecutar varias peticiones dentro del mismo test puede provocar comportamientos inesperados.
Si pasas el nombre de un guard como segundo argumento de actingAs(), se autenticará con ese guard. Además, ese guard pasa a ser el predeterminado durante la prueba.
$this->actingAs($user, 'web');
Para enviar una petición sin autenticar utiliza actingAsGuest().
Para comprobar que se lanza una excepción concreta, utiliza el facade Exceptions.
<?phpuse App\Exceptions\InvalidOrderException;use Illuminate\Support\Facades\Exceptions;test('se lanza una excepción', function () { Exceptions::fake(); $response = $this->get('/order/1'); // Se ha lanzado la excepción Exceptions::assertReported(InvalidOrderException::class); // Verificar el contenido de la excepción Exceptions::assertReported(function (InvalidOrderException $e) { return $e->getMessage() === 'The order was invalid.'; });});
<?phpnamespace Tests\Feature;use App\Exceptions\InvalidOrderException;use Illuminate\Support\Facades\Exceptions;use Tests\TestCase;class ExampleTest extends TestCase{ public function test_exception_is_thrown(): void { Exceptions::fake(); $response = $this->get('/'); Exceptions::assertReported(InvalidOrderException::class); Exceptions::assertReported(function (InvalidOrderException $e) { return $e->getMessage() === 'The order was invalid.'; }); }}
Para comprobar que no se ha lanzado una excepción utiliza assertNotReported o assertNothingReported.
Para probar si el código dentro de una closure lanza una excepción, utiliza assertThrows().
$this->assertThrows( fn () => (new ProcessOrder)->execute(), OrderInvalid::class);// Comprobando también el contenido de la excepción$this->assertThrows( fn () => (new ProcessOrder)->execute(), fn (OrderInvalid $e) => $e->orderId() === 123);
Para comprobar que no se lanza excepción, utiliza assertDoesntThrow().
$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute());
Los datos de la respuesta JSON son accesibles como si fueran un array.
expect($response['created'])->toBeTrue();
$this->assertTrue($response['created']);
assertJson() convierte la respuesta a array y comprueba que el array indicado esté contenido en la respuesta JSON. Aunque la respuesta contenga otras propiedades, la prueba pasa si aparece el fragmento indicado.
El método etc() permite que existan propiedades no aseveradas. Si no se usa etc(), la aserción falla si aparecen propiedades no verificadas. Así se evita filtrar accidentalmente información sensible en la respuesta.
Para comprobar la presencia o ausencia de propiedades utiliza has() y missing().
Con actingAs() puedes enviar peticiones como usuario autenticado.
<?phpuse App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase;uses(RefreshDatabase::class);test('un usuario autenticado puede ver el dashboard', function () { $user = User::factory()->create(); $response = $this->actingAs($user)->get('/dashboard'); $response->assertStatus(200);});test('los usuarios no autenticados son redirigidos', function () { $response = $this->get('/dashboard'); $response->assertRedirect('/login');});
<?phpnamespace Tests\Feature;use App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase;use Tests\TestCase;class DashboardTest extends TestCase{ use RefreshDatabase; public function test_authenticated_users_can_view_the_dashboard(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->get('/dashboard'); $response->assertStatus(200); } public function test_unauthenticated_users_are_redirected(): void { $response = $this->get('/dashboard'); $response->assertRedirect('/login'); }}
Para autenticar con un guard concreto, pasa su nombre como segundo argumento.
Con withSession() puedes preparar la sesión antes de la petición.
Con assertSessionHas() compruebas la existencia de valores en la sesión.
<?phptest('validar valor de sesión', function () { $response = $this->withSession(['locale' => 'es'])->get('/'); $response->assertSessionHas('locale', 'es');});
<?phpnamespace Tests\Feature;use Tests\TestCase;class SessionTest extends TestCase{ public function test_session_value(): void { $response = $this->withSession(['locale' => 'es'])->get('/'); $response->assertSessionHas('locale', 'es'); }}
El método fake() de la clase Illuminate\Http\UploadedFile genera archivos e imágenes ficticios.
Combinado con Storage::fake() puedes probar la subida de archivos con facilidad.
<?phpuse Illuminate\Http\UploadedFile;use Illuminate\Support\Facades\Storage;test('se puede subir un avatar', function () { Storage::fake('avatars'); $file = UploadedFile::fake()->image('avatar.jpg'); $response = $this->post('/avatar', [ 'avatar' => $file, ]); Storage::disk('avatars')->assertExists($file->hashName());});
Puedes indicar el tamaño de la imagen y del archivo, útil para probar reglas de validación.
// Ancho, alto y tamaño (en KB)UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100);// Crear un archivo de cualquier tipoUploadedFile::fake()->create('document.pdf', $sizeInKilobytes);// Indicar el MIME de forma explícitaUploadedFile::fake()->create( 'document.pdf', $sizeInKilobytes, 'application/pdf');
Puedes probar vistas renderizándolas directamente sin simular la petición HTTP.
view() acepta el nombre de la vista y, opcionalmente, un array de datos, y devuelve una instancia de Illuminate\Testing\TestView.
<?phptest('la vista welcome se renderiza', function () { $view = $this->view('welcome', ['name' => 'Taylor']); $view->assertSee('Taylor');});
<?phpnamespace Tests\Feature;use Tests\TestCase;class ViewTest extends TestCase{ public function test_a_welcome_view_can_be_rendered(): void { $view = $this->view('welcome', ['name' => 'Taylor']); $view->assertSee('Taylor'); }}
La clase TestView expone los siguientes métodos de aserción.
Método
Descripción
assertSee($value)
La vista contiene la cadena indicada
assertSeeInOrder([...])
La vista contiene las cadenas indicadas en ese orden
assertSeeText($value)
El texto de la vista contiene la cadena indicada
assertSeeTextInOrder([...])
El texto contiene las cadenas indicadas en ese orden
assertDontSee($value)
La vista no contiene la cadena indicada
assertDontSeeText($value)
El texto no contiene la cadena indicada
Para obtener el HTML renderizado como cadena, castea la instancia TestView.
$contents = (string) $this->view('welcome');
Para pasar errores de validación a la vista utiliza withViewErrors().
$view = $this->withViewErrors([ 'name' => ['Introduce un nombre válido.']])->view('form');$view->assertSee('Introduce un nombre válido.');
Las pruebas HTTP encajan muy bien con TDD (desarrollo guiado por tests). Ten en cuenta los siguientes puntos para sacarles el máximo partido.
Empieza por tests de tipo Feature
Los tests HTTP se sitúan en tests/Feature/. Empezar por el comportamiento externo (petición → respuesta) deja claro qué funcionalidad debes implementar.
Usa RefreshDatabase
En los tests que usen base de datos, aplica el trait RefreshDatabase. La base de datos se reinicia tras cada prueba y se evitan interferencias entre tests, lo que permite construir una suite estable independiente del orden de ejecución.
Aprovecha las factorías
Con User::factory()->create() y demás factorías es muy fácil crear datos de prueba. Definir estados (state) para casos concretos mejora mucho la legibilidad.
Un test, una aserción
Cada método de test debería verificar, en la medida de lo posible, una única cosa. Así resulta más fácil identificar la causa cuando falla. Aplicar el patrón AAA (Arrange, Act, Assert) hace los tests más legibles.
No olvides los tests de autenticación
En las rutas que requieran autenticación, cubre siempre tanto el caso «autenticado» como el «no autenticado». Detectarás pronto problemas de seguridad.