Por qué las pruebas sin mocks son como cocinar sin sal
\n\nImagina que estás desarrollando una aplicación que envía notificaciones por email, trabaja con una base de datos y consume una API externa. ¿Cómo probarías esto sin mocks y fixtures en PHP? Tendrías que enviar correos reales cada vez, crear registros de prueba en la BD y esperar la respuesta del servicio externo. Es lento, poco fiable y, francamente, una mala práctica.
\n\nAquí es donde entran los test doubles — sustitutos de objetos reales. En el ecosistema PHP, la combinación ganadora es: mocks para verificar interacciones y fixtures para preparar los datos. Veamos cómo funciona esto en la práctica.
\n\nMock vs Stub vs Fake: no los confundas
\n\nAntes de meternos en código, aclaremos conceptos. En el mundo de las pruebas unitarias en PHP, existen tres tipos principales de sustitutos:
\n\n- \n
- Stub — devuelve datos predefinidos. Verificas que tu código procesa esos datos correctamente. \n
- Mock — no solo devuelve datos, sino que también verifica cómo tu sistema llama a los métodos: cuántas veces, con qué argumentos y en qué orden. \n
- Fake — una implementación simplificada pero funcional (por ejemplo, una base de datos en memoria). \n
En PHPUnit, el estándar de facto para testing en PHP, estos conceptos a menudo se mezclan. Pero seamos precisos: los mocks en PHP son aquellos donde defines expectativas (expectations).
\n\nEscribiendo tu primer mock con PHPUnit
\n\nSupongamos que tenemos una clase Mailer que envía correos. Queremos verificar que el servicio de registro llama al método send() exactamente una vez con los datos correctos.
<?php\nuse PHPUnit\\Framework\\TestCase;\n\nclass UserRegistrationTest extends TestCase\n{\n public function testSendWelcomeEmailOnRegistration(): void\n {\n // Creamos un mock para Mailer\n $mailer = $this->createMock(Mailer::class);\n \n // Configuramos la expectativa: el método send se llamará 1 vez\n $mailer->expects($this->once())\n ->method('send')\n ->with(\n $this->equalTo('user@example.com'),\n $this->stringContains('Bienvenido')\n );\n \n $service = new UserRegistrationService($mailer);\n $service->register('user@example.com', 'password123');\n }\n}\n\n\nObserva: no verificamos que el correo se haya enviado realmente. Verificamos que nuestro código intentó enviarlo — esa es la esencia de los mocks en pruebas PHP. Si el método send() no se llama (o se llama dos veces), el test fallará.
El método expects y sus posibilidades
\n\nPHPUnit ofrece una API completa para configurar expectativas:
\n\n- \n
$this->once()— exactamente una llamada \n $this->exactly(3)— exactamente tres llamadas \n $this->atLeastOnce()— al menos una llamada \n $this->never()— el método no debe ser llamado \n
Para los argumentos, usa matchers (restricciones):
\n\n$mailer->expects($this->once())\n ->method('send')\n ->with(\n $this->callback(function($email) {\n return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;\n }),\n $this->anything() // el segundo argumento no nos importa\n );\n\n\nFixtures en PHP: prepara los datos antes de que los necesites
\n\nLos fixtures son el estado predefinido de los datos en el que se encuentra el sistema antes de ejecutar una prueba. En PHPUnit, se utilizan los métodos setUp() y