# Pest Plugin Arch Pest Plugin Arch is an architecture testing plugin for Pest PHP that enables developers to enforce architectural rules and constraints in their PHP projects. It provides a fluent API for defining and validating architectural boundaries, dependency rules, and code organization patterns. The plugin helps maintain clean architecture by catching architectural violations early in the development process through automated tests. This plugin extends Pest's testing framework with specialized expectations for checking class relationships, dependency directions, naming conventions, and layered architecture compliance. It integrates seamlessly with Pest's existing test syntax and leverages PHP's reflection capabilities along with static analysis to verify that your codebase adheres to defined architectural principles. The plugin supports testing classes, interfaces, traits, enums, and even user-defined functions. ## Core API Functions ### arch() - Define Architecture Tests Creates an architecture test with automatic grouping under the 'arch' test group. ```php expect('App\Controllers') ->classes->toBeFinal(); // Test with multiple expectations chained arch('strict types') ->expect('*') ->toUseStrictTypes(); // Complex architecture rule arch('base') ->expect('Pest\Arch') ->classes->toBeFinal() ->classes->not->toBeAbstract() ->toOnlyUse([ 'Pest', 'PHPUnit\Architecture', 'Symfony\Component\Finder\Finder', ])->ignoring(['PHPUnit\Framework', 'Composer']); ``` ### expect()->toOnlyUse() - Enforce Dependency Constraints Ensures a class or namespace only depends on specified dependencies. ```php toOnlyUse([Fooable::class, Storable::class]); // Namespace-level dependency check expect('Tests\Fixtures\Models') ->toOnlyUse('Tests\Fixtures\Contracts\Models'); // Allow nothing except specified dependencies expect('Pest\Arch\Collections') ->toOnlyUse('Pest\Arch\ValueObjects'); // With ignoring specific dependencies expect(Product::class) ->toOnlyUse([]) ->ignoring('Tests\Fixtures\Contracts'); // Opposite assertion expect('Pest\Arch\Exceptions') ->not->toOnlyUse(['InvalidDependency']); ``` ### expect()->toUse() - Check Dependency Usage Verifies that a class or namespace uses specific dependencies. ```php toUse([Color::class]); // Check vendor dependency usage expect(DependsOnVendor::class) ->toOnlyUse('Pest') ->toOnlyUse('Pest\Support') ->toOnlyUse('Pest\Support\Str'); // Check global function usage expect('Tests\Fixtures\Misc\DependOnGlobalFunctions') ->toUse('my_request_global_function') ->not->toUse('Tests\Fixtures\my_request_namespaced_function'); // Check namespaced function usage expect('Tests\Fixtures\Misc\DependOnNamespacedFunctions') ->toUse('Tests\Fixtures\my_request_namespaced_function') ->not->toUse('my_request_global_function'); ``` ### expect()->toUseNothing() - Enforce Zero Dependencies Asserts that a class or namespace has no external dependencies. ```php toUseNothing(); // Verify namespace has no dependencies expect('App\Repositories') ->toUseNothing(); // Enum with no dependencies expect(Color::class) ->toUseNothing(); // With exceptions using ignoring expect('Pest\Arch\ValueObjects') ->toUseNothing() ->ignoring(['PHPUnit\Framework', 'Pest\Expectation']); ``` ### expect()->toImplement() - Check Interface Implementation Verifies that classes implement specific interfaces. ```php toImplement(Indexable::class); // Negative assertion expect(ProductController::class) ->not->toImplement(Indexable::class); // Namespace-level check with ignoring expect('Tests\Fixtures\Controllers') ->toImplement(Indexable::class) ->ignoring(ProductController::class); // Check exceptions implement Throwable expect('Pest\Arch\Exceptions') ->toImplement(Throwable::class); ``` ### expect()->toExtend() - Check Class Inheritance Ensures classes extend a specific parent class. ```php toExtend(Controller::class); // Negative assertion expect(ProductController::class) ->not->toExtend(Controller::class); // Namespace check with ignoring expect('Tests\Fixtures\Controllers') ->toExtend(Controller::class) ->ignoring(ProductController::class); ``` ### expect()->toExtendNothing() - Enforce No Inheritance Verifies that classes don't extend any parent class. ```php classes->toExtendNothing() ->ignoring([ VendorObjectDescription::class, ObjectDescription::class, ]); // Namespace-level check expect('App\ValueObjects') ->toExtendNothing(); ``` ### expect()->toBeFinal() - Enforce Final Classes Checks that classes are declared as final. ```php toBeFinal(); // Negative assertion expect(UserController::class) ->not->toBeFinal(); // Namespace-level check expect('Tests\Fixtures\Controllers') ->toBeFinal() ->ignoring(UserController::class); // All classes in namespace are final expect('Pest\Arch') ->classes->toBeFinal(); ``` ### expect()->toHaveSuffix() - Enforce Naming Conventions Verifies that classes follow naming suffix conventions. ```php toHaveSuffix('Controller'); // Single class has suffix expect(UserController::class) ->toHaveSuffix('Controller'); // Services namespace convention expect('App\Services') ->toHaveSuffix('Service'); // Repository naming pattern expect('App\Repositories') ->toHaveSuffix('Repository'); ``` ### expect()->toBeUsedIn() - Check Usage by Other Classes Verifies that a class or interface is used in specific locations. ```php toBeUsedIn('Tests') ->not->toBeUsedIn('App\Production'); // Check interface usage expect('Tests\Fixtures\Contracts\Models\Fooable') ->toBeUsedIn('Tests\Fixtures\Models'); // Check trait usage expect('App\Concerns\HasTimestamps') ->toBeUsedIn('App\Models'); ``` ### expect()->toOnlyBeUsedIn() - Restrict Usage Locations Ensures a class or interface is only used in specified locations. ```php toOnlyBeUsedIn([ 'Tests\Fixtures\Models', 'Tests\Fixtures\Services', ]); // Restrict function usage expect('die') ->toOnlyBeUsedIn('Tests'); // Internal class usage restriction expect('App\Internal\Configuration') ->toOnlyBeUsedIn('App\Bootstrap'); ``` ### expect()->toBeUsedInNothing() - Enforce No Usage Verifies that a class, interface, or function is not used anywhere. ```php toBeUsedInNothing(); // Function not used expect('sleep') ->toBeUsedInNothing(); // With ignoring specific usages expect(Fooable::class) ->toBeUsedInNothing() ->ignoring('Tests\Fixtures\Models'); // Alternative syntax expect(Fooable::class) ->not->toBeUsed(); ``` ### Filtering Modifiers - Target Specific Code Types Apply filters to narrow down architectural expectations to specific code types. ```php extending(Controller::class) ->toExtend(Controller::class); // Test only classes implementing an interface expect('Tests\Fixtures\Controllers') ->implementing(Indexable::class) ->toImplement(Indexable::class); // Test only classes using a trait expect('Tests\Fixtures\Controllers') ->using(HasResponses::class) ->toUseTrait(HasResponses::class); // Test only abstract classes expect('Tests\Fixtures\Misc\Abstracts') ->abstractClasses() ->toHaveMethod('edible'); // Test only interfaces expect('App') ->interfaces() ->toHaveSuffix('Interface'); // Test only traits expect('App\Concerns') ->traits() ->toHaveSuffix('Trait'); // Test only enums expect('App\Enums') ->enums() ->toUseNothing(); // Test only classes (not interfaces/traits) expect('Pest\Arch') ->classes->toBeFinal() ->classes->not->toBeAbstract(); ``` ### Wildcard Namespace Patterns - Test Multiple Layers Use wildcards to match multiple namespaces across different architectural layers. ```php getTargets() ->toBe([ 'Tests\Fixtures\Domains\A\Models\Article', 'Tests\Fixtures\Domains\B\Models\Article', ]); // Multiple wildcards - matches multiple levels expect('Tests\Fixtures\Domains\*\*\Models') ->getTargets() ->toBe([ 'Tests\Fixtures\Domains\A\Contracts\Models\Bazable', 'Tests\Fixtures\Domains\B\Contracts\Models\Bazable', ]); // Enforce layer isolation with wildcards expect('App\Modules\*\Domain') ->toOnlyUse('App\Modules\*\Domain') ->not->toUse('App\Modules\*\Infrastructure'); ``` ### Global Configuration - Test-Wide Settings Configure global options that apply across all architecture tests. ```php beforeEach(function () { $this->arch()->ignore([ 'NunoMaduro\Collision', 'Illuminate\Support', ]); })->in(__DIR__); // Ignore global functions in specific test expect('Tests\Fixtures\Misc\DependOnGlobalFunctions') ->not ->toUse('my_request_global_function') ->ignoringGlobalFunctions(); ``` ## Integration and Testing Patterns Pest Plugin Arch is designed to integrate seamlessly into your existing Pest test suite, providing architectural guardrails without disrupting your development workflow. Architecture tests are automatically grouped under the 'arch' tag, allowing you to run them separately or as part of your full test suite. The plugin works by analyzing your codebase using PHP reflection and static analysis, detecting dependencies through use statements, type hints, and instantiations. All violations are reported with precise file and line number information, making it easy to locate and fix architectural issues. The plugin excels at enforcing layered architecture patterns such as hexagonal architecture, onion architecture, and clean architecture. You can define strict boundaries between domain, application, and infrastructure layers, ensuring that dependencies flow in the correct direction. It supports modular monoliths by allowing wildcard patterns to check that modules remain isolated from each other. The fluent API enables both positive assertions (what code should do) and negative assertions (what code should not do), giving you complete control over your architectural rules. Combined with Pest's test organization features, you can create comprehensive architectural test suites that document and enforce your team's design decisions automatically.