¿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 nombrePresets: 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
ControllerLos Controllers solo tengan métodos REST (
index,show,create,store,edit,update,destroy)Los Models estén en
App\ModelsLos 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 namespaceReglas ú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 App7. 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 reglasEjecutar solo tests de arquitectura
./vendor/bin/pest --filter=archO si agrupaste los tests:
./vendor/bin/pest tests/ArchitectureConclusió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á.
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.
star Incluido en cualquier suscripción