Testing en Laravel 2026: Pest, Mocking y Preguntas Clave para Entrevistas Tecnicas

Guia completa de testing en Laravel con Pest para 2026: tests unitarios, tests funcionales, mocking de facades, tests de arquitectura, mutation testing y preguntas frecuentes en entrevistas tecnicas para desarrolladores PHP.

Guia tecnica sobre testing en Laravel con Pest que cubre mocking, tests de arquitectura y preparacion para entrevistas tecnicas 2026

La calidad del software PHP depende hoy tanto de la solidez de los tests como de la elegancia del codigo de produccion. En el ecosistema Laravel, Pest se consolido como el framework de testing dominante, desplazando al uso directo de PHPUnit en la mayoria de los proyectos profesionales de la region. En 2026, dominar Pest, el mocking de facades y los tests de arquitectura representa un requisito tecnico innegociable en los procesos de seleccion de empresas tecnologicas de America Latina. Los equipos que despliegan Laravel en produccion esperan que los candidatos sepan escribir tests expresivos, mantener una cobertura significativa y estructurar sus suites de pruebas de manera profesional.

Este articulo recorre en profundidad las practicas de testing en Laravel con Pest, desde la configuracion inicial hasta el mutation testing, pasando por el mocking avanzado y los tests de arquitectura. Cada seccion corresponde a una competencia directamente evaluable en entrevistas tecnicas, acompanada de ejemplos de codigo aplicables de inmediato en un ejercicio practico o en un proyecto real.

Por que Pest en lugar de PHPUnit directo?

Pest se construye sobre PHPUnit pero ofrece una sintaxis expresiva inspirada en Jest y RSpec. La cadena expect()->toBe() reemplaza las aserciones clasicas como assertEquals(), los bloques describe/it estructuran los tests de forma legible, y las funcionalidades nativas como los tests de arquitectura y el mutation testing reducen la dependencia de paquetes externos. Pest mantiene compatibilidad total con PHPUnit: cualquier test PHPUnit existente funciona sin modificaciones dentro de una suite Pest.

Configuracion de Pest en un proyecto Laravel

La configuracion de Pest en un proyecto Laravel se centraliza en el archivo tests/Pest.php, donde se definen los traits y las clases base que se aplican automaticamente a cada archivo de test. Esta centralizacion evita la repeticion de declaraciones comunes y garantiza la coherencia del entorno de pruebas en toda la suite.

tests/Pest.phpphp
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()
    ->extend(Tests\TestCase::class)
    ->use(RefreshDatabase::class)
    ->in('Feature');

El metodo extend() asocia la clase TestCase de Laravel a todos los tests, proporcionando acceso a los metodos HTTP (getJson, postJson), la autenticacion simulada (actingAs) y las aserciones de respuesta. El trait RefreshDatabase reinicia la base de datos entre cada test mediante transacciones, lo que garantiza aislamiento completo sin el costo de recargar las migraciones en cada ejecucion.

La directiva ->in('Feature') limita la aplicacion de esta configuracion a los tests funcionales. Los tests unitarios ubicados en tests/Unit/ heredan un entorno mas liviano, sin conexion a la base de datos, lo que acelera su ejecucion. Esta separacion arquitectural entre tests unitarios y funcionales refleja la piramide de tests clasica y constituye un punto de evaluacion habitual en entrevistas.

Tests funcionales: validacion de endpoints HTTP

Los tests funcionales en Laravel verifican el comportamiento completo de un endpoint, desde la solicitud HTTP entrante hasta la respuesta JSON o la redireccion. Pest simplifica la escritura de estos tests gracias a una sintaxis fluida que permite encadenar aserciones de estado, aserciones de estructura JSON y verificaciones en base de datos.

El siguiente test valida el registro de un usuario a traves de una API REST.

tests/Feature/UserRegistrationTest.phpphp
use App\Models\User;

it('registers a new user with valid data', function () {
    $response = $this->postJson('/api/register', [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => 'SecurePass123!',
        'password_confirmation' => 'SecurePass123!',
    ]);

    $response->assertStatus(201)
        ->assertJsonStructure(['user' => ['id', 'name', 'email']]);

    expect(User::where('email', 'jane@example.com')->exists())->toBeTrue();
});

Varios elementos merecen atencion particular. El metodo postJson() envia una solicitud POST con los headers JSON configurados automaticamente, simulando un cliente API real. La asercion assertJsonStructure valida la presencia de las claves esperadas sin imponer valores especificos, lo que hace al test resiliente ante modificaciones menores de la respuesta. La asercion final mediante expect() confirma que el usuario fue efectivamente persistido en la base de datos, verificando asi toda la cadena de procesamiento.

Este enfoque de test de extremo a extremo a nivel HTTP constituye el estandar en Laravel. Valida simultaneamente el enrutamiento, el middleware, la validacion de datos, la logica del controlador y la persistencia. Una falla en cualquier capa se manifiesta en el test, ofreciendo una red de seguridad completa para las refactorizaciones.

Tests unitarios con Pest: logica de negocio aislada

Los tests unitarios apuntan a clases o metodos individuales, sin interaccion con la base de datos, el sistema de archivos ni los servicios externos. Se ejecutan en milisegundos y constituyen la base mas confiable de la piramide de tests. El bloque describe/it de Pest estructura estos tests de forma legible.

tests/Unit/PriceCalculatorTest.phpphp
use App\Services\PriceCalculator;

describe('PriceCalculator', function () {
    it('applies a percentage discount correctly', function () {
        $calculator = new PriceCalculator();

        // 20% off a 150.00 base price
        $result = $calculator->applyDiscount(150.00, 20);

        expect($result)->toBe(120.00);
    });

    it('rejects negative discount values', function () {
        $calculator = new PriceCalculator();

        expect(fn () => $calculator->applyDiscount(100.00, -5))
            ->toThrow(InvalidArgumentException::class);
    });
});

El bloque describe agrupa los tests relacionados con una misma clase o funcionalidad, facilitando la lectura del reporte de ejecucion. La asercion toThrow() valida que una excepcion se lanza correctamente ante entradas invalidas, un patron defensivo esencial en la logica de negocio. El paso de un closure a expect() permite capturar la excepcion sin interrumpir el test.

Durante una entrevista tecnica, la capacidad de distinguir lo que corresponde al test unitario (logica de calculo, transformacion de datos, validacion de reglas de negocio) de lo que corresponde al test funcional (interaccion HTTP, integracion con la base de datos) constituye un indicador clave de madurez tecnica.

Mocking de facades Laravel: Mail, Queue y Notifications

Laravel provee un sistema de facades que simplifica el acceso a los servicios del framework. Para los tests, estas facades ofrecen metodos fake() que reemplazan la implementacion real por un doble de prueba capaz de registrar las llamadas recibidas sin producir efectos secundarios. Este mecanismo es fundamental para probar procesos que envian correos, despachan jobs o disparan notificaciones.

tests/Feature/OrderProcessingTest.phpphp
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
use App\Mail\OrderConfirmation;
use App\Jobs\ProcessPayment;

it('dispatches payment job and sends confirmation email', function () {
    // Fake both Mail and Queue facades
    Mail::fake();
    Queue::fake();

    $user = User::factory()->create();
    $order = Order::factory()->for($user)->create([
        'total' => 99.99,
        'status' => 'pending',
    ]);

    // Act: confirm the order via HTTP
    $this->actingAs($user)
        ->postJson("/api/orders/{$order->id}/confirm")
        ->assertOk();

    // Assert: payment job was dispatched with correct amount
    Queue::assertPushed(ProcessPayment::class, function ($job) use ($order) {
        return $job->order->id === $order->id
            && $job->order->total === 99.99;
    });

    // Assert: confirmation email was sent to the user
    Mail::assertSent(OrderConfirmation::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
});

Las llamadas a Mail::fake() y Queue::fake() interceptan respectivamente los envios de correo y el despacho de jobs durante toda la duracion del test. Ningun correo real se envia, ningun job se ejecuta en segundo plano. Las aserciones assertPushed y assertSent verifican despues que las clases correctas fueron despachadas con los parametros esperados.

El closure pasado a Queue::assertPushed ilustra un patron avanzado: permite verificar no solo que el job fue despachado, sino tambien que sus propiedades contienen los valores correctos. Esta granularidad en las aserciones detecta errores sutiles donde se pasa el objeto equivocado al job, un error comun durante las refactorizaciones.

Este tipo de test aparece sistematicamente en entrevistas tecnicas de Laravel. La respuesta esperada demuestra la comprension de que las facades son proxies hacia servicios resolubles a traves del contenedor de inyeccion de dependencias, y que el modo fake() reemplaza temporalmente el servicio real por un espia de prueba.

Mocking avanzado con Mockery: servicios externos

Para los servicios que no son facades de Laravel, Mockery ofrece un control granular sobre el comportamiento de los dobles de prueba. Este caso se presenta frecuentemente con los clientes de servicios de terceros como Stripe, Twilio o APIs internas.

tests/Feature/PaymentGatewayTest.phpphp
use App\Services\PaymentGateway;
use App\Services\StripeClient;

it('charges the customer through the payment gateway', function () {
    // Create a mock of the Stripe client
    $stripeClient = Mockery::mock(StripeClient::class);
    $stripeClient->shouldReceive('charge')
        ->once()
        ->with('cus_abc123', 5000, 'usd')
        ->andReturn(['status' => 'succeeded', 'id' => 'ch_xyz']);

    // Bind the mock in the container
    $this->app->instance(StripeClient::class, $stripeClient);

    $gateway = app(PaymentGateway::class);
    $result = $gateway->processCharge('cus_abc123', 50.00);

    expect($result['status'])->toBe('succeeded');
});

El metodo Mockery::mock() crea un doble de prueba que intercepta las llamadas a metodos. El encadenamiento shouldReceive()->once()->with()->andReturn() define un contrato estricto: el metodo charge debe ser llamado exactamente una vez, con los parametros especificados, y devolvera el valor definido. Cualquier desviacion provoca un fallo del test.

La instruccion $this->app->instance() registra el mock en el contenedor de servicios de Laravel. Cuando PaymentGateway solicita una instancia de StripeClient por inyeccion de dependencias, el contenedor entrega el mock en lugar de la implementacion real. Este mecanismo se basa en el principio de inversion de dependencias y constituye la clave de la testabilidad en aplicaciones Laravel bien estructuradas.

La distincion entre el monto pasado a processCharge (50.00 en dolares) y el pasado a charge (5000 en centavos) revela una logica de conversion en el PaymentGateway. El test valida implicitamente esta transformacion, detectando cualquier regresion en el calculo sin necesidad de inspeccionar directamente el codigo de la clase.

¿Listo para aprobar tus entrevistas de Laravel?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Tests de arquitectura: imponer las convenciones del proyecto

Pest introduce una funcionalidad unica en el ecosistema PHP: los tests de arquitectura. Estos tests verifican que el codigo fuente respeta convenciones estructurales definidas por el equipo, sin ejecutar la logica de la aplicacion. Las violaciones se detectan de forma estatica, a la manera de un linter avanzado.

tests/Architecture/ArchitectureTest.phpphp
arch('controllers do not use Eloquent directly')
    ->expect('App\Http\Controllers')
    ->not->toUse('Illuminate\Database\Eloquent');

arch('services are final classes')
    ->expect('App\Services')
    ->toBeFinal();

arch('no debugging functions in production code')
    ->expect(['dd', 'dump', 'var_dump', 'ray'])
    ->not->toBeUsed();

El primer test impone una separacion arquitectural estricta: los controladores no deben manipular Eloquent directamente, forzando el uso de una capa de servicios o repositorios. Esta restriccion previene la acumulacion de logica de negocio en los controladores, un anti-patron clasico de las aplicaciones Laravel mal estructuradas.

El segundo test garantiza que las clases de servicio estan declaradas como final, impidiendo la herencia descontrolada que complejiza el mantenimiento. El tercero detecta funciones de depuracion olvidadas en el codigo de produccion, un problema que provoca fugas de informacion en ambientes productivos.

Pest ofrece tambien presets de arquitectura preconfigurados que cubren las convenciones estandar de Laravel.

tests/Architecture/LaravelPresetTest.phpphp
arch()->preset()->laravel();
arch()->preset()->security();
arch()->preset()->php();

El preset laravel() verifica que los controladores extiendan la clase base correcta, que los modelos se encuentren en el namespace adecuado y que las convenciones de nomenclatura se respeten. El preset security() detecta el uso de funciones peligrosas como eval(), exec() o shell_exec(). El preset php() impone las buenas practicas generales del lenguaje.

En entrevistas, el conocimiento de los tests de arquitectura distingue a los candidatos que tienen una vision global de la calidad del software de aquellos que se limitan a los tests funcionales. Estos tests codifican las decisiones arquitecturales del equipo y las hacen ejecutables automaticamente en el pipeline de CI.

Mutation testing: evaluar la calidad real de los tests

La cobertura de codigo mide que lineas se ejecutan durante los tests, pero no dice nada sobre la calidad de las aserciones. El mutation testing cubre esa brecha modificando el codigo fuente de manera sistematica (reemplazo de operadores, eliminacion de condiciones, modificacion de valores de retorno) y verificando que los tests detecten cada mutacion. Un test que pasa a pesar de una mutacion es un test con aserciones insuficientemente precisas.

Pest integra el mutation testing de forma nativa desde la version 3, haciendo accesible esta practica sin configuracion adicional.

bash
# Run mutation testing on a specific class
php artisan test --mutate --class=App\\Services\\PriceCalculator

El comando analiza la clase objetivo, genera mutantes y ejecuta la suite de tests para cada uno de ellos. El reporte indica el score de mutacion: el porcentaje de mutantes detectados por los tests existentes.

El siguiente ejemplo ilustra la diferencia entre un test con alta cobertura pero bajo score de mutacion y un test verdaderamente robusto.

php
// Example: this test has high coverage but low mutation score
it('calculates shipping cost', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Only checks that the result is numeric
    expect($cost)->toBeFloat();
});

// Improved: catches mutations by asserting the exact value
it('calculates domestic shipping for 5kg package', function () {
    $cost = calculateShipping(weight: 5.0, zone: 'domestic');

    // Exact assertion catches operator and value mutations
    expect($cost)->toBe(12.50);
});

El primer test pasa incluso si la formula de calculo se modifica por completo: cualquier numero de punto flotante satisface la asercion toBeFloat(). El segundo test impone el valor exacto esperado, detectando cualquier mutacion en la logica de calculo. Esta distincion entre aserciones amplias y aserciones precisas representa un concepto fundamental que los reclutadores evaluan con regularidad.

El mutation testing revela las zonas del codigo donde la cobertura cuantitativa enmascara una debilidad cualitativa. Un score de mutacion del 90% o superior indica una suite de tests verdaderamente confiable, capaz de detectar regresiones sutiles que una cobertura de lineas al 100% dejaria pasar.

Escenarios de tests completos para la entrevista

Las entrevistas tecnicas de Laravel proponen frecuentemente ejercicios de testing sobre escenarios realistas: publicacion de articulos, autenticacion de APIs, validacion de formularios. Los siguientes ejemplos cubren estos casos de uso habituales.

tests/Feature/ArticlePublishingTest.phpphp
use App\Models\Article;
use App\Models\User;

it('publishes a draft article and updates the timestamp', function () {
    $author = User::factory()->create(['role' => 'editor']);

    $article = Article::factory()
        ->for($author, 'author')
        ->draft()
        ->create(['title' => 'Testing Best Practices']);

    $this->actingAs($author)
        ->patchJson("/api/articles/{$article->id}/publish")
        ->assertOk()
        ->assertJsonPath('data.status', 'published');

    $article->refresh();

    expect($article->status)->toBe('published')
        ->and($article->published_at)->not->toBeNull()
        ->and($article->published_at->isToday())->toBeTrue();
});

Este test valida un flujo completo de publicacion. La factory draft() crea un articulo en estado borrador, simulando el contexto inicial. La asercion assertJsonPath verifica un valor preciso en la respuesta JSON sin imponer la estructura completa. La llamada a refresh() recarga el modelo desde la base de datos, garantizando que las aserciones se apliquen sobre el estado persistido y no sobre un cache local.

La cadena de aserciones de Pest con ->and() permite verificar multiples propiedades en una sola expresion fluida, mejorando la legibilidad del test. La verificacion isToday() utiliza las capacidades de Carbon integradas en Laravel para validar que el timestamp de publicacion corresponde al dia actual.

tests/Feature/ApiAuthenticationTest.phpphp
use App\Models\User;

describe('API Authentication', function () {
    it('rejects unauthenticated requests with 401', function () {
        $this->getJson('/api/profile')
            ->assertUnauthorized();
    });

    it('returns the authenticated user profile', function () {
        $user = User::factory()->create([
            'name' => 'John Doe',
            'email' => 'john@example.com',
        ]);

        $this->actingAs($user)
            ->getJson('/api/profile')
            ->assertOk()
            ->assertJson([
                'data' => [
                    'name' => 'John Doe',
                    'email' => 'john@example.com',
                ],
            ]);
    });

    it('validates required fields on registration', function () {
        $this->postJson('/api/register', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['name', 'email', 'password']);
    });
});

Este grupo de tests cubre los tres escenarios fundamentales de toda API autenticada: el rechazo de solicitudes no autenticadas, la respuesta correcta para un usuario conectado, y la validacion de campos obligatorios. La asercion assertUnauthorized() verifica el estatus 401, mientras que assertUnprocessable() corresponde al estatus 422 estandar para errores de validacion.

El uso de assertJsonValidationErrors valida que los errores de validacion estan correctamente formateados y asociados a los campos correspondientes. Este patron es indispensable para las APIs consumidas por clientes frontend que muestran los mensajes de error debajo de cada campo de formulario.

Preguntas de entrevista tecnica sobre testing en Laravel

Las preguntas de testing en entrevistas de Laravel evaluan la comprension de los mecanismos subyacentes, no la simple memorizacion de la sintaxis. Los siguientes puntos cubren los temas que se abordan con mayor frecuencia.

Cual es la diferencia entre RefreshDatabase y DatabaseMigrations? El trait RefreshDatabase utiliza transacciones de base de datos para revertir los cambios despues de cada test, lo que resulta significativamente mas rapido. DatabaseMigrations ejecuta las migraciones completas antes de cada test y trunca las tablas despues, lo cual es mas lento pero necesario en ciertos casos donde las transacciones no son suficientes, por ejemplo con tests que involucran multiples conexiones de base de datos.

Por que usar factories en lugar de inserciones manuales? Las factories encapsulan la creacion de objetos de prueba con valores por defecto coherentes. Permiten especificar unicamente los atributos relevantes para el test en curso, haciendo explicita la intencion de la prueba. Las factories gestionan automaticamente las relaciones entre modelos a traves de los metodos for() y has(), eliminando el codigo repetitivo de creacion de datos.

Como probar un middleware sin probar todo el controlador? Laravel permite instanciar un middleware directamente y llamar a su metodo handle() con una solicitud fabricada. Este enfoque aisla la logica del middleware de la del controlador. Alternativamente, un test funcional dirigido con aserciones sobre el estatus HTTP permite verificar el comportamiento del middleware en contexto.

Cuando conviene usar Mockery en lugar de los fakes de facades? Los fakes de facades (Mail::fake(), Queue::fake()) son suficientes para los servicios integrados en Laravel. Mockery es necesario para los servicios de terceros (clientes de API, SDKs externos) que no son facades. La regla general: usar los fakes de Laravel cuando existen, Mockery para todo lo demas.

Que valida un test de arquitectura que los tests clasicos no cubren? Los tests de arquitectura verifican la estructura y las dependencias del codigo sin ejecutarlo. Detectan violaciones de convenciones (un controlador que usa Eloquent directamente, una funcion de debug olvidada, un servicio no declarado como final) que los tests funcionales no pueden captar porque el codigo funciona correctamente a pesar de la violacion arquitectural.

Como interpretar un score de mutacion bajo a pesar de una cobertura de codigo alta? Un score de mutacion bajo con cobertura alta significa que los tests ejecutan el codigo pero no verifican sus resultados con precision. La solucion consiste en reemplazar las aserciones amplias (toBeFloat, toBeArray) por aserciones precisas (toBe(12.50), toHaveCount(3)) que detecten las modificaciones en la logica de negocio.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Conclusion

El testing en Laravel con Pest en 2026 representa mucho mas que una habilidad tecnica adicional: constituye un pilar de la credibilidad profesional de un desarrollador PHP. La capacidad de escribir tests expresivos, hacer mock correctamente de las dependencias externas y mantener convenciones arquitecturales automatizadas distingue a los perfiles senior de los desarrolladores intermedios.

Los puntos esenciales a retener para las entrevistas tecnicas:

  • Configuracion de Pest: el archivo tests/Pest.php centraliza los traits y las clases base, separando claramente tests unitarios y funcionales
  • Tests funcionales: la combinacion postJson / assertStatus / expect cubre toda la cadena de procesamiento de un endpoint
  • Mocking de facades: Mail::fake(), Queue::fake() y las aserciones asociadas constituyen el patron estandar para probar procesos asincronos
  • Mockery: para servicios de terceros, la inyeccion del mock a traves del contenedor de servicios respeta el principio de inversion de dependencias
  • Tests de arquitectura: codifican las decisiones estructurales del equipo y las hacen ejecutables dentro del pipeline de CI/CD
  • Mutation testing: el score de mutacion revela la calidad real de las aserciones, mas alla de la simple cobertura de lineas
  • Factories y aserciones: el dominio de las factories de Laravel y las aserciones JSON granulares acelera la escritura de tests mantenibles

En un proceso de seleccion de Laravel, la demostracion de un enfoque estructurado del testing transforma un ejercicio tecnico en una vitrina de competencias profesionales. Cada test bien escrito evidencia una comprension profunda del framework y una rigurosidad que genera confianza en los equipos tecnicos sobre la calidad del codigo futuro.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#laravel
#testing
#pest
#php
#interview

Compartir

Artículos relacionados