Saltar al contenido principal

Introducción

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.
<?php

test('la aplicación devuelve una respuesta correcta', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});
<?php

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

Cómo crear peticiones

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.
<?php

test('petición básica', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});
<?php

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

Personalizar cabeceras

Con withHeaders() puedes personalizar las cabeceras de la petición.
<?php

test('petición con cabeceras', function () {
    $response = $this->withHeaders([
        'X-Header' => 'Value',
    ])->post('/user', ['name' => 'Sally']);

    $response->assertStatus(201);
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_headers(): void
    {
        $response = $this->withHeaders([
            'X-Header' => 'Value',
        ])->post('/user', ['name' => 'Sally']);

        $response->assertStatus(201);
    }
}

Cookies

Los métodos withCookie() o withCookies() permiten definir cookies antes de la petición.
<?php

test('petición con cookies', function () {
    $response = $this->withCookie('color', 'blue')->get('/');

    $response = $this->withCookies([
        'color' => 'blue',
        'name'  => 'Taylor',
    ])->get('/');
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_cookies(): void
    {
        $response = $this->withCookie('color', 'blue')->get('/');

        $response = $this->withCookies([
            'color' => 'blue',
            'name'  => 'Taylor',
        ])->get('/');
    }
}

Sesión y autenticación

Con withSession() puedes preparar los datos de sesión antes de la petición.
<?php

test('petición con sesión', function () {
    $response = $this->withSession(['banned' => false])->get('/');
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_the_session(): void
    {
        $response = $this->withSession(['banned' => false])->get('/');
    }
}
actingAs() envía la petición como un usuario autenticado. Se combina con las factorías de modelos.
<?php

use App\Models\User;

test('acción que requiere autenticación', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user)
        ->withSession(['banned' => false])
        ->get('/');
});
<?php

namespace Tests\Feature;

use App\Models\User;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_an_action_that_requires_authentication(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)
            ->withSession(['banned' => false])
            ->get('/');
    }
}
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().
$this->actingAsGuest();

Depurar la respuesta

Cuando quieras inspeccionar el contenido de la respuesta durante un test, usa dump, dumpHeaders o dumpSession.
<?php

test('depurar respuesta', function () {
    $response = $this->get('/');

    $response->dump();
    $response->dumpHeaders();
    $response->dumpSession();
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_debug_response(): void
    {
        $response = $this->get('/');

        $response->dump();
        $response->dumpHeaders();
        $response->dumpSession();
    }
}
Para detener la ejecución al mismo tiempo, utiliza dd, ddHeaders, ddBody, ddJson y ddSession.
$response->dd();
$response->ddHeaders();
$response->ddBody();
$response->ddJson();
$response->ddSession();

Probar excepciones

Para comprobar que se lanza una excepción concreta, utiliza el facade Exceptions.
<?php

use 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.';
    });
});
<?php

namespace 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.
Exceptions::assertNotReported(InvalidOrderException::class);

Exceptions::assertNothingReported();
Para enviar la petición desactivando el manejo de excepciones, utiliza withoutExceptionHandling().
$response = $this->withoutExceptionHandling()->get('/');
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());

Pruebas de API JSON

Laravel ofrece muchos helpers para probar APIs JSON. Utiliza los métodos json, getJson, postJson, putJson, patchJson, deleteJson y optionsJson.
<?php

test('petición a la API', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
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.

Aserción de coincidencia exacta

Con assertExactJson() verificas que el JSON devuelto coincide exactamente con el array indicado.
<?php

test('aserción JSON exacta', function () {
    $response = $this->postJson('/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertExactJson([
            'created' => true,
        ]);
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_asserting_an_exact_json_match(): void
    {
        $response = $this->postJson('/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertExactJson([
                'created' => true,
            ]);
    }
}

Aserción por path JSON

Con assertJsonPath() verificas el valor en un path concreto.
<?php

test('aserción por path JSON', function () {
    $response = $this->postJson('/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJsonPath('team.owner.name', 'Darian');
});
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_asserting_a_json_paths_value(): void
    {
        $response = $this->postJson('/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJsonPath('team.owner.name', 'Darian');
    }
}
También puedes pasar una closure para una validación más flexible.
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);

Pruebas JSON fluidas

Pasando una closure a assertJson() puedes escribir aserciones fluidas mediante una instancia de AssertableJson.
use Illuminate\Testing\Fluent\AssertableJson;

test('prueba JSON fluida', function () {
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('[email protected]'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
});
use Illuminate\Testing\Fluent\AssertableJson;

public function test_fluent_json(): void
{
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('[email protected]'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
}
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().
$response->assertJson(fn (AssertableJson $json) =>
    $json->has('data')
        ->missing('message')
);
Para varias propiedades a la vez, hasAll() y missingAll().
$response->assertJson(fn (AssertableJson $json) =>
    $json->hasAll(['status', 'data'])
        ->missingAll(['message', 'code'])
);

Aserciones sobre colecciones JSON

Cuando la ruta devuelve una respuesta con varios elementos, has() permite comprobar el número de elementos y el contenido de la colección.
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has(3)
            ->first(fn (AssertableJson $json) =>
                $json->where('id', 1)
                    ->where('name', 'Victoria Faith')
                    ->missing('password')
                    ->etc()
            )
    );
Para aplicar la misma aserción a todos los elementos, usa each().
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has(3)
            ->each(fn (AssertableJson $json) =>
                $json->whereType('id', 'integer')
                    ->whereType('name', 'string')
                    ->whereType('email', 'string')
                    ->missing('password')
                    ->etc()
            )
    );

Aserción de tipos JSON

Con whereType() y whereAllType() puedes validar el tipo de las propiedades.
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('id', 'integer')
        ->whereAllType([
            'users.0.name' => 'string',
            'meta' => 'array'
        ])
);
Se pueden combinar varios tipos con |. La aserción pasa si el valor coincide con alguno.
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('name', 'string|null')
        ->whereType('id', ['string', 'integer'])
);
Los tipos disponibles son string, integer, double, boolean, array y null.

Tests de autenticación

Con actingAs() puedes enviar peticiones como usuario autenticado.
<?php

use 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');
});
<?php

namespace 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.
$this->actingAs($user, 'api');

Ejemplo: prueba del flujo de registro de usuarios

Ejemplo de test del endpoint de registro de usuarios.
<?php

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('un usuario puede registrarse', function () {
    $response = $this->post('/register', [
        'name'                  => 'Test User',
        'email'                 => '[email protected]',
        'password'              => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertRedirect('/dashboard');
    $this->assertDatabaseHas('users', ['email' => '[email protected]']);
});

test('no se puede registrar con un email duplicado', function () {
    User::factory()->create(['email' => '[email protected]']);

    $response = $this->post('/register', [
        'name'                  => 'Test User',
        'email'                 => '[email protected]',
        'password'              => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertSessionHasErrors('email');
});
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class RegistrationTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_register(): void
    {
        $response = $this->post('/register', [
            'name'                  => 'Test User',
            'email'                 => '[email protected]',
            'password'              => 'password',
            'password_confirmation' => 'password',
        ]);

        $response->assertRedirect('/dashboard');
        $this->assertDatabaseHas('users', ['email' => '[email protected]']);
    }

    public function test_duplicate_email_cannot_register(): void
    {
        User::factory()->create(['email' => '[email protected]']);

        $response = $this->post('/register', [
            'name'                  => 'Test User',
            'email'                 => '[email protected]',
            'password'              => 'password',
            'password_confirmation' => 'password',
        ]);

        $response->assertSessionHasErrors('email');
    }
}

Pruebas de sesión

Con withSession() puedes preparar la sesión antes de la petición. Con assertSessionHas() compruebas la existencia de valores en la sesión.
<?php

test('validar valor de sesión', function () {
    $response = $this->withSession(['locale' => 'es'])->get('/');

    $response->assertSessionHas('locale', 'es');
});
<?php

namespace 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');
    }
}

Aserciones sobre la sesión

MétodoDescripción
assertSessionHas($key, $value)La sesión contiene la clave y el valor indicados
assertSessionHasAll([...])La sesión contiene varios pares clave/valor
assertSessionHasErrors($keys)La sesión contiene errores de validación
assertSessionHasErrorsIn($bag, $keys)Existen errores en el error bag indicado
assertSessionHasNoErrors()La sesión no contiene errores de validación
assertSessionDoesntHaveErrors()La sesión no contiene errores de validación
assertSessionMissing($key)La sesión no contiene la clave indicada

Pruebas de subida de archivos

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.
<?php

use 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());
});
<?php

namespace Tests\Feature;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class AvatarTest extends TestCase
{
    public function test_avatars_can_be_uploaded(): void
    {
        Storage::fake('avatars');

        $file = UploadedFile::fake()->image('avatar.jpg');

        $response = $this->post('/avatar', [
            'avatar' => $file,
        ]);

        Storage::disk('avatars')->assertExists($file->hashName());
    }
}
Para comprobar que un archivo no existe utiliza assertMissing().
Storage::disk('avatars')->assertMissing('missing.jpg');

Personalizar el archivo ficticio

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 tipo
UploadedFile::fake()->create('document.pdf', $sizeInKilobytes);

// Indicar el MIME de forma explícita
UploadedFile::fake()->create(
    'document.pdf', $sizeInKilobytes, 'application/pdf'
);

Pruebas de vistas

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.
<?php

test('la vista welcome se renderiza', function () {
    $view = $this->view('welcome', ['name' => 'Taylor']);

    $view->assertSee('Taylor');
});
<?php

namespace 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étodoDescripció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.');

Probar componentes

Con blade() puedes renderizar una plantilla Blade en bruto.
$view = $this->blade(
    '<x-component :name="$name" />',
    ['name' => 'Taylor']
);

$view->assertSee('Taylor');
Con component() renderizas un componente Blade y obtienes una instancia de Illuminate\Testing\TestComponent.
$view = $this->component(Profile::class, ['name' => 'Taylor']);

$view->assertSee('Taylor');

Lista de aserciones sobre la respuesta

Métodos principales que ofrece la clase Illuminate\Testing\TestResponse.

Estado HTTP

MétodoDescripción
assertOk()200 OK
assertCreated()201 Created
assertAccepted()202 Accepted
assertNoContent($status = 204)204 No Content
assertBadRequest()400 Bad Request
assertUnauthorized()401 Unauthorized
assertForbidden()403 Forbidden
assertNotFound()404 Not Found
assertUnprocessable()422 Unprocessable Entity
assertTooManyRequests()429 Too Many Requests
assertInternalServerError()500 Internal Server Error
assertStatus($code)El código indicado
assertSuccessful()Cualquier 2xx
assertClientError()Cualquier 4xx
assertServerError()Cualquier 5xx

Redirecciones

MétodoDescripción
assertRedirect($uri)Redirige a la URI indicada
assertRedirectContains($string)La URI de redirección contiene la cadena indicada
assertRedirectToRoute($name, $params)Redirige a la ruta con nombre indicada
assertRedirectToSignedRoute($name, $params)Redirige a la ruta firmada indicada
assertRedirectBack()Redirige a la URL anterior

Contenido

MétodoDescripción
assertSee($value, $escaped = true)La respuesta contiene el valor indicado
assertSeeInOrder(array $values)Los valores aparecen en ese orden
assertSeeText($value)La respuesta contiene el texto indicado
assertDontSee($value)La respuesta no contiene el valor indicado
assertDontSeeText($value)La respuesta no contiene el texto indicado
assertContent($value)El cuerpo coincide con el valor indicado
assertDownload($filename)Es una respuesta de descarga

JSON

MétodoDescripción
assertJson(array $data)La respuesta JSON contiene los datos indicados
assertExactJson(array $data)La respuesta JSON coincide exactamente
assertJsonPath($path, $value)El valor del path indicado coincide
assertJsonMissingPath($path)El path indicado no existe
assertJsonStructure(array $structure)La respuesta tiene la estructura indicada
assertJsonCount($count, $key)La clave contiene el número indicado de elementos
assertJsonFragment(array $data)La respuesta contiene el fragmento indicado
assertJsonMissing(array $data)La respuesta no contiene los datos indicados
assertJsonIsArray()La respuesta es un array
assertJsonIsObject()La respuesta es un objeto
assertJsonValidationErrors($keys)Contiene errores de validación en las claves indicadas
assertJsonMissingValidationErrors($keys)No contiene errores de validación en las claves indicadas

Cabeceras y cookies

MétodoDescripción
assertHeader($headerName, $value)Existe la cabecera indicada
assertHeaderMissing($headerName)No existe la cabecera indicada
assertCookie($name, $value)Existe la cookie indicada
assertCookieExpired($name)La cookie indicada está expirada
assertCookieMissing($name)No existe la cookie indicada
assertPlainCookie($name, $value)Existe la cookie sin cifrar indicada

Vistas

MétodoDescripción
assertViewIs($value)Se devolvió la vista indicada
assertViewHas($key, $value)La vista contiene el dato indicado
assertViewHasAll(array $data)La vista contiene todos los datos indicados
assertViewMissing($key)La vista no contiene la clave indicada

Validación

MétodoDescripción
assertValid($keys)No hay errores de validación en las claves indicadas
assertInvalid($keys)Hay errores de validación en las claves indicadas

Recomendaciones para hacer TDD

Las pruebas HTTP encajan muy bien con TDD (desarrollo guiado por tests). Ten en cuenta los siguientes puntos para sacarles el máximo partido.
Los tests HTTP se sitúan en tests/Feature/. Empezar por el comportamiento externo (petición → respuesta) deja claro qué funcionalidad debes implementar.
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.
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.
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.
En las rutas que requieran autenticación, cubre siempre tanto el caso «autenticado» como el «no autenticado». Detectarás pronto problemas de seguridad.

Páginas relacionadas

Introducción a los tests

Repasa los fundamentos del testing en Laravel y el uso de php artisan test.
Última modificación el 13 de julio de 2026