Mocks y stubs con Mockery
Aísle unidades con dobles de prueba flexibles
Mocks y stubs con Mockery es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.
Por qué usar una biblioteca de mocking dedicada
PHPUnit incluye su propia API de dobles de prueba, pero Mockery ofrece una sintaxis más fluida y expresiva, además de funcionalidades que los mocks de PHPUnit no tienen: mocks parciales, spies, coincidencia flexible de argumentos y expectativas ordenadas. Para código con muchas colaboraciones, Mockery suele resultar mucho más legible. En esta lección se cubre todo el espectro de dobles y cuándo usar cada uno.
composer require --dev mockery/mockeryStub, mock o spy
Un vocabulario preciso evita pruebas confusas:
- Stub — devuelve valores predefinidos; se comprueba el estado (el resultado).
- Mock — tiene expectativas sobre las llamadas; se comprueba el comportamiento (que se llamó correctamente). Si no se cumplen las expectativas, la prueba falla.
- Spy — registra las llamadas y permite comprobarlas después de que ocurran.
Regla práctica: prefiera stubs para consultas y mocks/spies para comandos.
Un stub con allows()
Use Mockery::mock() y allows() (o shouldReceive()->andReturn()) para proporcionar valores de retorno predefinidos sin comprobar que la llamada se haya realizado. Aquí, un stub de gateway proporciona un tipo de cambio conocido para que podamos probar el cálculo de forma aislada.
<?php
use Mockery;
use PHPUnit\Framework\TestCase;
final class ConverterTest extends TestCase
{
public function test_converts_using_rate(): void
{
$rates = Mockery::mock(RateGateway::class);
$rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);
$sut = new Converter($rates);
self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));
Mockery::close();
}
}
Un mock con expects()
Cuando la propia interacción forma parte del contrato — por ejemplo, «se debe enviar un correo exactamente una vez» — use expects() o shouldReceive()->once(). Mockery verifica la expectativa durante Mockery::close(); si no se realiza la llamada esperada, la prueba falla.
<?php
use Mockery;
$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
->send(Mockery::type(Message::class))
->once();
$service = new SignupService($mailer);
$service->register('ada@example.com');
Mockery::close(); // fails here if send() was never called
Matchers de argumentos
Los matchers de Mockery permiten que las expectativas sean tan flexibles o estrictas como sea necesario:
Mockery::any()— cualquier valor.Mockery::type('string')/ un nombre de clase — comprobación de tipo.Mockery::on(fn($a) => ...)— predicado personalizado.Mockery::capture($var)— captura el argumento para comprobarlo posteriormente.
<?php
use Mockery;
$repo = Mockery::mock(UserRepo::class);
$repo->expects()
->save(Mockery::on(fn(User $u) => $u->isActive()))
->once()
->andReturnTrue();
Secuencias de retorno y retornos dinámicos
Puede programar varios valores de retorno para llamadas sucesivas o calcular el retorno a partir de los argumentos con andReturnUsing(). Esto permite modelar la lógica de reintentos, la paginación o colaboradores con estado.
<?php
use Mockery;
$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
->twice()
->andThrow(new \RuntimeException('timeout'))
->andReturn('{"ok":true}');
// Or derive the return from the input:
$api->shouldReceive('echo')
->andReturnUsing(fn(string $in) => strtoupper($in));
Spies: compruebe después de la llamada
Un spy invierte el orden: actúe primero y compruebe después. Mockery::spy() registra las llamadas; después puede consultarlas con shouldHaveReceived(). Los spies mantienen limpia la estructura Arrange-Act-Assert cuando no quiere que las expectativas preestablecidas recarguen la preparación.
<?php
use Mockery;
$logger = Mockery::spy(Logger::class);
$service = new PaymentService($logger);
$service->charge(500);
// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
->with(Mockery::pattern('/charged 500/'))
->once();
Mockery::close();
Mocks parciales
A veces querrá un objeto real, pero con un método sobrescrito: un mock parcial. El método makePartial() de Mockery llama a los métodos reales, excepto a aquellos sobre los que haya establecido expectativas. Úselos con moderación: depender demasiado de los mocks parciales suele indicar que una clase hace demasiadas cosas.
<?php
use Mockery;
$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);
// real summarize() runs, using the stubbed data
$summary = $report->summarize();
No haga mock de lo que no controla
Un principio central de las pruebas: evite hacer mocks directamente de clases de terceros. Sus API pueden cambiar y el mock puede alejarse silenciosamente de la realidad. En su lugar, envuélvalas detrás de su propia interfaz y cree el mock de esa interfaz. Así, el mock verifica su contrato y una prueba de integración del adaptador verifica la implementación real.
<?php
interface PaymentGateway { // you own this
public function charge(int $cents, string $token): string;
}
final class StripeGateway implements PaymentGateway {
public function __construct(private \Stripe\StripeClient $client) {}
public function charge(int $cents, string $token): string {
return $this->client->paymentIntents->create([/* ... */])->id;
}
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.
Cierre siempre y compruebe los recuentos
Dos obligaciones operativas:
- Llame a
Mockery::close()entearDown()(o use el traitMockeryPHPUnitIntegration) para que las expectativas se verifiquen realmente y se limpien los elementos globales. - Use recuentos explícitos (
once(),times(n),never()): los mocks imprecisos permiten que los errores pasen inadvertidos.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
final class OrderServiceTest extends TestCase
{
use MockeryPHPUnitIntegration; // auto-calls Mockery::close()
public function test_does_not_refund_paid_orders(): void
{
$gw = \Mockery::mock(PaymentGateway::class);
$gw->shouldReceive('refund')->never();
// ... exercise SUT ...
}
}
Expectativas ordenadas
En ocasiones, el orden de las llamadas forma parte del contrato: debe llamar a beginTransaction() antes de commit(). El método ordered() de Mockery impone la secuencia y hace que la prueba falle si las llamadas llegan fuera de orden. Úselo solo cuando el orden importe realmente; especificarlo en exceso hace que las pruebas sean frágiles.
<?php
use Mockery;
$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();
$service = new TransferService($tx);
$service->run();
Mockery::close(); // fails if commit() happened before begin()
Comprobación rápida
¿Stub o mock? ¿Cuál usar en cada caso?
Resumen
Ha dominado los dobles de Mockery:
- Stubs (
allows) para consultas; mocks (expects) para comandos; spies para comprobaciones posteriores. - Los matchers de argumentos (
type,on,capture) ajustan el nivel de estrictez. - Las secuencias de retorno y
andReturnUsingmodelan colaboradores con estado o dinámicos. - Los mocks parciales sobrescriben métodos individuales; úselos con moderación.
- No haga mock de lo que no controla: envuelva las dependencias de terceros detrás de su interfaz.
- Llame siempre a
Mockery::close()y compruebe recuentos de llamadas explícitos.
A continuación: pruebas de integración y funcionales.
Preguntas frecuentes
¿La lección «Mocks y stubs con Mockery» es gratis?
Sí — el texto completo de «Mocks y stubs con Mockery» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Mocks y stubs con Mockery»?
Aísle unidades con dobles de prueba flexibles Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar PHP Academy?
No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Mocks y stubs con Mockery»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de PHP Academy?
Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- El flujo de trabajo del desarrollo guiado por pruebas
- Mocks y stubs con Mockery
- Pruebas de integración y funcionales
- Pruebas de mutación con Infection