Ir al contenido principal
Laravel, shipping fast.
Capitulo 8 · Pruebas y calidad

Factories que describen intencion

Julian Beaujardin

Una factory no es un fixture. Es documentación de cómo es un registro válido, escrita como código en lugar de como prosa.

// api-infrastructure src/Database/Factories/BearerFactory.php
final class BearerFactory extends Factory
{
    protected $model = Bearer::class;

    public function definition(): array
    {
        return [
            'token' => Str::random(32),
            'description' => $this->faker->sentence(),
            'domains' => null,
            'settings' => [],
            'expires_at' => null,
        ];
    }

    public function expired(): self
    {
        return $this->state(fn (array $attributes) => [
            'expires_at' => now()->subDay(),
        ]);
    }
}

definition() es la línea base: un token válido, sin expirar y sin restricciones, exactamente el registro que querrías por defecto en una prueba que no va de tokens. El método de estado expired() es donde las factories se ganan la palabra «intención». Compara Bearer::factory()->expired()->create() con construir el mismo registro a mano en cada prueba que lo necesite. La versión con factory se lee como una frase. La versión a mano obliga a cada persona a volver a deducir qué significa «expirado», y cuando el modelo gane una columna obligatoria nueva, todas esas llamadas se rompen a la vez en lugar de que una definición de factory absorba el cambio.

Probar el fallo, no solo el exito

Una suite que sólo demuestra que el camino feliz funciona ha demostrado que tu API funciona cuando no va nada mal, que es la única condición de la que no necesitas pruebas. Los fallos interesantes viven en qué pasa cuando Statamic va lento, se equivoca o desaparece.

// tests/Feature/LicenseControllerTest.php
test('treats a 404 from Statamic as a successful no-op delete', function () {
    Http::swap(new Factory);
    Http::fake(fn () => Http::response(['message' => 'Not found'], 404));

    $api = new StatamicAPI(Http::baseUrl('https://example.test')->acceptJson());

    // Already-deleted key: returns null and does NOT throw.
    expect($api->deleteLicense('already-gone'))->toBeNull();
});

test('rethrows non-404 Statamic delete failures so the job can retry', function () {
    Http::swap(new Factory);
    Http::fake(fn () => Http::response(['message' => 'Server error'], 500));

    $api = new StatamicAPI(Http::baseUrl('https://example.test')->acceptJson());

    expect(fn () => $api->deleteLicense('boom'))->toThrow(RequestException::class);
});

Estas dos pruebas parecen casi idénticas y significan cosas opuestas, que es justo el punto. Un 404 en un borrado significa que lo que querías eliminar ya no está, así que se trata como éxito. Un 500 significa que Statamic se rompió, así que el mismo método relanza y deja que la política de reintentos del job se encargue. Colapsa las dos en una sola prueba de «maneja los errores con elegancia» y nunca notarías que alguien intercambió los dos comportamientos por accidente. Escríbelas por separado y la distinción pasa a formar parte de la especificación, permanentemente, tanto si alguien recuerda el razonamiento dentro de seis meses como si no.

Cada endpoint merece este trato para sus propios modos de fallo: tokens expirados, tokens revocados, cargas externas malformadas, fallos de validación en cada campo obligatorio y no sólo en uno. Si sólo has probado la versión donde todo va bien, no has probado la API: has probado una demo.