Prueba del comando CLI
¡El capitán está harto de que la gente corra detrás del cohete porque llegan tarde! ¡Por eso hemos creado un comando para enviar correos electrónicos recordatorios! Problema resuelto! Ahora escribamos una prueba para asegurarnos de que sigue funcionando. "Nueva función, nueva prueba", ¡ese es mi lema!
Salta a tu terminal y ejecuta:
symfony console make:test
Teclea? KernelTestCase
. ¿Nombre? SendBookingRemindersCommandTest
.
SendBookingRemindersCommandTest
En nuestro IDE, la nueva clase se ha añadido a tests/
. Ábrelo y mueve la clase a un nuevo espacio de nombres: App\Tests\Functional\Command
, para mantener las cosas organizadas.
Perfecto. Primero, limpia las tripas y añade algunos rasgos de comportamiento:use ResetDatabase, Factories,
InteractsWithMailer:
// ... lines 1 - 9 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
use ResetDatabase, Factories, InteractsWithMailer; | |
// ... lines 13 - 22 | |
} |
Elimina dos pruebas:public function testNoRemindersSent()
con$this->markTestIncomplete()
ypublic function testRemindersSent()
. Márcalo también como incompleto:
// ... lines 1 - 9 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 12 - 13 | |
public function testNoRemindersSent() | |
{ | |
$this->markTestIncomplete(); | |
} | |
public function testRemindersSent() | |
{ | |
$this->markTestIncomplete(); | |
} | |
} |
De vuelta al terminal, ejecuta las pruebas con:
bin/phpunit
Lista de pruebas TODO
Fíjate, nuestras dos pruebas originales pasan, los dos puntos, y estas íes son las nuevas pruebas incompletas. Me encanta esta pauta: escribe los stubs de prueba para una nueva función, y luego juega a eliminar los incompletos uno a uno hasta que desaparezcan todos. Entonces, ¡la funcionalidad está terminada!
Symfony tiene algunas herramientas para probar comandos, pero me gusta usar un paquete que las envuelve en una experiencia más agradable. Instálalo con:
zenstruck/console-test
composer require --dev zenstruck/console-test
Para activar los ayudantes de este paquete, añade un nuevo rasgo de comportamiento a nuestra prueba:InteractsWithConsole
:
// ... lines 1 - 10 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
use ResetDatabase, Factories, InteractsWithMailer, InteractsWithConsole; | |
// ... lines 14 - 26 | |
} |
¡Estamos listos para derribar esos yoes!
testNoRemindersSent()
La primera prueba es fácil: queremos asegurarnos de que, cuando no hay reservas que recordar, el comando no envía ningún correo electrónico. Escribe$this->executeConsoleCommand()
y sólo el nombre del comando: app:send-booking-reminders
. Asegúrate de que el comando se ejecuta correctamente con ->assertSuccessful()
y->assertOutputContains('Sent 0 booking reminders')
:
// ... lines 1 - 10 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 13 - 14 | |
public function testNoRemindersSent() | |
{ | |
$this->executeConsoleCommand('app:send-booking-reminders') | |
->assertSuccessful() | |
->assertOutputContains('Sent 0 booking reminders') | |
; | |
} | |
// ... lines 22 - 26 | |
} |
testRemindersSent()
Organiza
Pasamos a la siguiente prueba Ésta es más complicada: tenemos que crear una reserva que pueda recibir un recordatorio. Crea el arreglo de la reserva con$booking = BookingFactory::createOne()
. Pasa un array con'trip' => TripFactory::new()
, y dentro de éste, otro array con'name' => 'Visit Mars'
, 'slug' => 'iss'
(para evitar el problema de la imagen). La reserva también necesita un cliente: 'customer' => CustomerFactory::new()
. Lo único que nos importa es el correo electrónico del cliente: 'email' =>
'steve@minecraft.com' por último, la fecha de la reserva: 'date' => new \DateTimeImmutable('+4 days')
:
// ... lines 1 - 14 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 17 - 26 | |
public function testRemindersSent() | |
{ | |
$booking = BookingFactory::createOne([ | |
'trip' => TripFactory::new([ | |
'name' => 'Visit Mars', | |
'slug' => 'iss', | |
]), | |
'customer' => CustomerFactory::new(['email' => 'steve@minecraft.com']), | |
'date' => new \DateTimeImmutable('+4 days'), | |
]); | |
// ... lines 37 - 56 | |
} | |
} |
¡Uf! Tenemos una reserva en la base de datos que necesita que se le envíe un recordatorio. El paso de configuración, u ordenación, de esta prueba está hecho.
Pre-Aserción
Añade una preafirmación para asegurarte de que no se ha enviado un recordatorio a esta reserva:$this->assertNull($booking->getReminderSentAt())
:
// ... lines 1 - 14 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 17 - 26 | |
public function testRemindersSent() | |
{ | |
// ... lines 29 - 37 | |
$this->assertNull($booking->getReminderSentAt()); | |
// ... lines 39 - 56 | |
} | |
} |
Actuar
Ahora el paso actuar:$this->executeConsoleCommand('app:send-booking-reminders')
->assertSuccessful()->assertOutputContains('Sent 1 booking
reminders') :
// ... lines 1 - 14 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 17 - 26 | |
public function testRemindersSent() | |
{ | |
// ... lines 29 - 39 | |
$this->executeConsoleCommand('app:send-booking-reminders') | |
->assertSuccessful() | |
->assertOutputContains('Sent 1 booking reminders') | |
; | |
// ... lines 44 - 56 | |
} | |
} |
Afirma
Pasamos a la fase de aserción para asegurarnos de que el correo electrónico se ha enviado. En BookingTest
, copia la aserción del correo electrónico y pégala aquí. Haz algunos ajustes: el correo electrónico es steve@minecraft.com
, el asunto es Booking Reminder for Visit Mars
y este correo no tiene ningún adjunto, así que elimina esa aserción por completo:
// ... lines 1 - 14 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 17 - 26 | |
public function testRemindersSent() | |
{ | |
// ... lines 29 - 44 | |
$this->mailer() | |
->assertSentEmailCount(1) | |
->assertEmailSentTo('steve@minecraft.com', function(TestEmail $email) { | |
->assertSubject('Booking Reminder for Visit Mars') | |
->assertContains('Visit Mars') | |
->assertContains('/booking/'.BookingFactory::first()->getUid()) | |
; | |
}) | |
; | |
// ... lines 55 - 56 | |
} | |
} |
Por último, escribe una aserción de que el comando actualizó la reserva en la base de datos.$this->assertNotNull($booking->getReminderSentAt())
:
// ... lines 1 - 14 | |
class SendBookingRemindersCommandTest extends KernelTestCase | |
{ | |
// ... lines 17 - 26 | |
public function testRemindersSent() | |
{ | |
// ... lines 29 - 55 | |
$this->assertNotNull($booking->getReminderSentAt()); | |
} | |
} |
¡El momento de la verdad! Ejecuta las pruebas:
bin/phpunit
¡Todo en verde!
Pruebas externas
Este tipo de pruebas externas me parecen muy divertidas y fáciles de escribir, porque no tienes que preocuparte demasiado de probar la lógica interna e imitan la forma en que un usuario interactúa con tu aplicación. No es casualidad que las afirmaciones se centren en lo que el usuario debería ver y en algunas comprobaciones de alto nivel posteriores a la interacción, como comprobar algo en la base de datos.
Ahora que tenemos pruebas para nuestras dos rutas de envío de correo electrónico, demos una vuelta de la victoria y refactoricemos con confianza para eliminar la duplicación.