Con sync como conexión de cola, un job despachado se ejecuta al momento dentro del test. Eso es lo correcto para la mayoría de los tests: «borrar una licencia» debería terminar con el proveedor habiendo recibido la orden de borrarla.
A veces lo que se pone a prueba es el despacho en sí. El endpoint de borrado promete un 202 y un job encolado. Si después el job funciona es otro test.
it('queues the deletion and answers 202', function () {
Queue::fake();
$consumer = actingAsConsumer('licenses:write');
$this->deleteJson('/api/licenses/lic_1')
->assertAccepted();
Queue::assertPushed(
DeleteLicense::class,
fn (DeleteLicense $job) => $job->key === 'lic_1'
&& $job->consumer->is($consumer),
);
});
Queue::fake() atrapa los jobs y no ejecuta ninguno. El closure comprueba lo que se le dio al job. La segunda condición es la lección del Capítulo 10 convertida en aserción: el job lleva al consumidor que lo pidió. Quita el argumento en el controlador y este test falla, que es exactamente la regresión que de otro modo aparecería como la licencia de un socio borrada con las credenciales de otro.
La otra mitad ejecuta el job directamente y comprueba lo que hace:
it('deletes the license at the provider', function () {
Http::fake(['*' => Http::response(status: 204)]);
$consumer = Consumer::factory()
->forPartner('tok')
->create();
(new DeleteLicense('lic_1', $consumer))->handle();
Http::assertSent(
fn ($request) => $request->method() === 'DELETE',
);
});
Entre los dos cubren la funcionalidad, y ninguno depende de un worker. Los eventos y las notificaciones se simulan igual, con Event::fake() y Notification::fake(). Pasa la clase que te interesa, como en Event::fake([LicenseIssued::class]), y solo se simula esa. Simúlalo todo y también silenciarás los eventos de modelo de los que dependen tus factories. (Documentación de Laravel: Events › Testing y Notifications › Testing.)
Un test, muchas entradas
Las reglas de validación son donde se define una frontera, y una frontera tiene muchos bordes. Escribir un test por cada entrada incorrecta es tan tedioso que nadie lo hace. Los datasets de Pest lo convierten en un solo test:
it('rejects an invalid license', function (
array $body,
string $field,
) {
actingAsConsumer('licenses:write');
$this->postJson('/api/licenses', $body)
->assertUnprocessable()
->assertJsonValidationErrors([$field]);
})->with([
'no name' => [['domain' => 'a.test'], 'name'],
'no domain' => [['name' => 'Acme'], 'domain'],
'name too long' => [
['name' => str_repeat('a', 101), 'domain' => 'a.t'],
'name',
],
'domain is an array' => [
['name' => 'Acme', 'domain' => ['a.test']],
'domain',
],
]);
Cada entrada se ejecuta como un test propio, con su etiqueta en la salida, así que un fallo dice qué borde se rompió. Añadir una regla al request significa añadir una línea aquí.
Las entradas que vale la pena tener son las de los límites: justo en el máximo y una más allá, el tipo equivocado, la clave que falta. Son las entradas que un consumidor real envía por accidente y un atacante envía a propósito. La última no es hipotética: un array donde se espera una cadena es la manera en que un prepareForValidation() descuidado convierte un 422 en un 500, y por eso el prepareForValidation() del Capítulo 2 comprueba primero el tipo.