Saltar al contenido

¿Alguna vez has abierto un proyecto Laravel después de meses y te has encontrado con Controllers que llaman directamente a otros Controllers, Models que disparan eventos y jobs, o funciones dd() olvidadas en producción?

Los tests de arquitectura de Pest te permiten definir reglas que validan automáticamente la estructura de tu código. Si alguien rompe una convención, el test falla.

Compatibilidad y requisitos

  • Pest: v3.0 o superior (funciona en v4)

  • PHP: 8.2 o superior

  • Laravel: 10.x, 11.x, 12.x

Qué es arch() y por qué usarlo

arch() es una función de Pest que permite escribir expectativas sobre la arquitectura de tu aplicación. En lugar de testear comportamiento, testeas estructura.

<?php

arch('no debug en producción')
    ->expect('App')
    ->not->toUse(['dd', 'dump', 'var_dump', 'ray']);

Este test fallará si cualquier archivo dentro del namespace App usa alguna de esas funciones. Esto es especialmente útil para detectar código que no debería llegar a producción.

Sintaxis básica

La estructura siempre es la misma:

<?php

arch('nombre descriptivo')
    ->expect('Namespace\A\Validar')
    ->expectativa();

Puedes encadenar múltiples expectativas:

<?php

arch('models siguen convenciones')
    ->expect('App\Models')
    ->toBeClasses()
    ->toExtend('Illuminate\Database\Eloquent\Model')
    ->toHaveSuffix('');  // Sin sufijo, solo el nombre

Presets: reglas predefinidas

Pest incluye presets que agrupan reglas comunes. Una línea y tienes docenas de validaciones.

Preset PHP

Valida buenas prácticas generales de PHP:

<?php

arch()->preset()->php();

Esto verifica que no uses die(), var_dump(), print_r(), funciones deprecadas de PHP, y más.

Preset Security

Detecta código potencialmente inseguro:

<?php

arch()->preset()->security();

Falla si usas eval(), md5() para passwords, serialize() con datos de usuario, etc.

Preset Laravel

Valida convenciones específicas de Laravel:

<?php

arch()->preset()->laravel();

Entre otras cosas, verifica que:

  • Los Controllers tengan sufijo Controller

  • Los Controllers solo tengan métodos REST (index, show, create, store, edit, update, destroy)

  • Los Models estén en App\Models

  • Los Providers extiendan ServiceProvider

Preset Strict

Para proyectos que quieren máxima rigurosidad:

<?php

arch()->preset()->strict();

Exige declare(strict_types=1) en todos los archivos y clases final por defecto.

Ignorar excepciones en presets

Si un preset es demasiado estricto para algún caso, puedes ignorar reglas específicas:

<?php

arch()->preset()->security()->ignoring('md5');  // Permitir md5 (quizás para hashes no críticos)

arch()->preset()->laravel()->ignoring('App\Http\Controllers\Api');  // Excluir namespace

Reglas útiles para proyectos Laravel

Estas son reglas que uso en mis proyectos y que considero esenciales:

1. Strict types en todo el proyecto

<?php

arch('strict types everywhere')
    ->expect('App')
    ->toUseStrictTypes();

2. No debug en código

<?php

arch('no debug functions')
    ->expect('App')
    ->not->toUse(['dd', 'dump', 'var_dump', 'print_r', 'ray', 'exit', 'die']);

3. Controllers limpios

<?php

arch('controllers are final')
    ->expect('App\Http\Controllers')
    ->toHaveSuffix('Controller')
    ->toBeFinal();

4. Models extienden Eloquent

<?php

arch('models extend eloquent')
    ->expect('App\Models')
    ->toBeClasses()
    ->toExtend('Illuminate\Database\Eloquent\Model');

5. Actions son invocables

Si usas el patrón Action:

<?php

arch('actions are invokable')
    ->expect('App\Actions')
    ->toBeInvokable();

6. Traits en su lugar

<?php

arch('traits in concerns')
    ->expect('App\*\Concerns')
    ->toBeTraits();

// Wildcard: cualquier subcarpeta Concerns dentro de App

7. Interfaces en Contracts

<?php

arch('interfaces in contracts')
    ->expect('App\Contracts')
    ->toBeInterfaces();

8. Enums son string-backed

<?php

arch('enums are string backed')
    ->expect('App\Enums')
    ->toBeStringBackedEnums();

9. Jobs implementan ShouldQueue

<?php

arch('jobs are queueable')
    ->expect('App\Jobs')
    ->toImplement('Illuminate\Contracts\Queue\ShouldQueue');

10. Value Objects son readonly

<?php

arch('value objects are immutable')
    ->expect('App\ValueObjects')
    ->toBeReadonly()
    ->toBeFinal();

11. Domain no conoce Infrastructure

Si aplicas arquitectura hexagonal o DDD:

<?php

arch('domain is isolated')
    ->expect('App\Domain')
    ->not->toUse('Illuminate\Http')
    ->not->toUse('Illuminate\Console');

12. Models solo usados en Repositories

<?php

arch('models used only in repositories')
    ->expect('App\Models')
    ->toOnlyBeUsedIn('App\Repositories');

13. Archivos no muy largos

<?php

arch('files are small')
    ->expect('App')
    ->toHaveLineCountLessThan(300);

14. Sin métodos protected en Services

<?php

arch('services have no protected methods')
    ->expect('App\Services')
    ->not->toHaveProtectedMethods();

15. Comparaciones estrictas

<?php

arch('use strict equality')
    ->expect('App')
    ->toUseStrictEquality();

Modificadores útiles

Filtrar por tipo

<?php

// Solo clases (ignorar traits, interfaces, enums)
arch('app')
    ->expect('App')
    ->classes()
    ->toBeFinal();

// Solo las que extienden Model
arch('models use soft deletes')
    ->expect('App')
    ->extending(Model::class)
    ->toUseTrait(SoftDeletes::class);

// Solo las que implementan una interface
arch('queueables use dispatchable')
    ->expect('App')
    ->implementing(ShouldQueue::class)
    ->toUseTrait(Dispatchable::class);

Crear presets personalizados

Si tienes reglas que usas en varios proyectos, puedes crear tu propio preset en tests/Pest.php:

<?php

pest()->presets()->custom('miempresa', function (array $namespaces) {
    return [
        expect($namespaces[0])
            ->toUseStrictTypes()
            ->not->toUse(['dd', 'dump', 'ray']),
        
        expect($namespaces[0] . '\Http\Controllers')
            ->toBeFinal()
            ->toHaveSuffix('Controller'),
        
        expect($namespaces[0] . '\Actions')
            ->toBeInvokable(),
    ];
});

Y usarlo así:

<?php

arch()->preset()->miempresa();

Organización recomendada

Crea un archivo dedicado tests/Architecture/ArchTest.php:

<?php

// Presets base
arch()->preset()->php();
arch()->preset()->security();
arch()->preset()->laravel();

// Reglas específicas del proyecto
arch('strict types')
    ->expect('App')
    ->toUseStrictTypes();

arch('no debug')
    ->expect('App')
    ->not->toUse(['dd', 'dump', 'var_dump', 'ray']);

arch('controllers')
    ->expect('App\Http\Controllers')
    ->toHaveSuffix('Controller')
    ->toBeFinal();

// ... más reglas

Ejecutar solo tests de arquitectura

./vendor/bin/pest --filter=arch

O si agrupaste los tests:

./vendor/bin/pest tests/Architecture

Conclusión

Los tests de arquitectura son una inversión mínima con un retorno enorme. Una vez configurados, validan automáticamente que tu código siga las convenciones del proyecto en cada commit.

Empieza con los presets básicos (php, security, laravel) y ve añadiendo reglas específicas según las necesidades de tu proyecto. Tu yo del futuro te lo agradecerá.

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje