# Architecture Testing Architecture testing enables you to specify expectations that test whether your application adheres to a set of architectural rules, helping you maintain a clean and sustainable codebase. The expectations are determined by either relative namespaces, fully qualified namespaces, or function names. Here is an example of how you can define an architectural rule: ```php arch() ->expect('App') ->toUseStrictTypes() ->not->toUse(['die', 'dd', 'dump']); arch() ->expect('App\Models') ->toBeClasses() ->toExtend('Illuminate\Database\Eloquent\Model') ->toOnlyBeUsedIn('App\Repositories') ->ignoring('App\Models\User'); arch() ->expect('App\Http') ->toOnlyBeUsedIn('App\Http'); arch() ->expect('App\*\Traits') ->toBeTraits(); arch()->preset()->php(); arch()->preset()->security()->ignoring('md5'); ``` Now, let's dive into the various methods and modifiers available for architectural testing. In this section, you will learn: - [Expectations](#expectations): Allows to specify granular architectural rules. - [Presets](#presets): Allows to use predefined sets of granular architectural rules. - [Modifiers](#modifiers): To exclude or ignore certain types of files, classes, functions or lines of code. ## Expectations Granular expectations allow you to define specific architectural rules for your application. Here are the available expectations:
- [`toBeAbstract()`](#expect-toBeAbstract) - [`toBeCasedCorrectly()`](#expect-toBeCasedCorrectly) - [`toBeClasses()`](#expect-toBeClasses) - [`toBeEnums()`](#expect-toBeEnums) - [`toBeIntBackedEnums()`](#expect-toBeIntBackedEnums) - [`toBeInterfaces()`](#expect-toBeInterfaces) - [`toBeInvokable()`](#expect-toBeInvokable) - [`toBeFinal()`](#expect-toBeFinal) - [`toBeReadonly()`](#expect-toBeReadonly) - [`toBeStringBackedEnums()`](#expect-toBeStringBackedEnums) - [`toBeTraits()`](#expect-toBeTraits) - [`toBeUsed()`](#expect-toBeUsed) - [`toBeUsedIn()`](#expect-toBeUsedIn) - [`toExtend()`](#expect-toExtend) - [`toExtendNothing()`](#expect-toExtendNothing) - [`toImplement()`](#expect-toImplement) - [`toImplementNothing()`](#expect-toImplementNothing) - [`toHaveMethodsDocumented()`](#expect-toHaveMethodsDocumented) - [`toHavePropertiesDocumented()`](#expect-toHavePropertiesDocumented) - [`toHaveAttribute()`](#expect-toHaveAttribute) - [`toHaveFileSystemPermissions()`](#expect-toHaveFileSystemPermissions) - [`toHaveLineCountLessThan()`](#expect-toHaveLineCountLessThan) - [`toHaveMethod()`](#expect-toHaveMethod) - [`toHaveMethods()`](#expect-toHaveMethod) - [`toHavePrivateMethodsBesides()`](#expect-toHavePrivateMethodsBesides) - [`toHavePrivateMethods()`](#expect-toHavePrivateMethods) - [`toHaveProtectedMethodsBesides()`](#expect-toHaveProtectedMethodsBesides) - [`toHaveProtectedMethods()`](#expect-toHaveProtectedMethods) - [`toHavePublicMethodsBesides()`](#expect-toHavePublicMethodsBesides) - [`toHavePublicMethods()`](#expect-toHavePublicMethods) - [`toHavePrefix()`](#expect-toHavePrefix) - [`toHaveSuffix()`](#expect-toHaveSuffix) - [`toHaveSuspiciousCharacters()`](#expect-toHaveSuspiciousCharacters) - [`toHaveConstructor()`](#expect-toHaveConstructor) - [`toHaveDestructor()`](#expect-toHaveDestructor) - [`toOnlyImplement()`](#expect-toOnlyImplement) - [`toOnlyUse()`](#expect-toOnlyUse) - [`toOnlyBeUsedIn()`](#expect-toOnlyBeUsedIn) - [`toUse()`](#expect-toUse) - [`toUseStrictEquality()`](#expect-toUseStrictEquality) - [`toUseTrait()`](#expect-toUseTrait) - [`toUseTraits()`](#expect-toUseTraits) - [`toUseNothing()`](#expect-toUseNothing) - [`toUseStrictTypes()`](#expect-toUseStrictTypes)
### `toBeAbstract()` The `toBeAbstract()` method may be used to ensure that all classes within a given namespace are abstract. ```php arch('app') ->expect('App\Models') ->toBeAbstract(); ``` ### `toBeCasedCorrectly()` The `toBeCasedCorrectly()` method may be used to ensure that all class names match their file and directory path casing, verifying PSR-4 autoloading compliance. ```php arch('app') ->expect('App') ->toBeCasedCorrectly(); ``` For example, if a class is named `App\Models\UserProfile`, this expectation verifies that the file is located at `app/Models/UserProfile.php` — and not `app/Models/Userprofile.php` or `app/models/UserProfile.php`. ### `toBeClasses()` The `toBeClasses()` method may be used to ensure that all files within a given namespace are classes. ```php arch('app') ->expect('App\Models') ->toBeClasses(); ``` ### `toBeEnums()` The `toBeEnums()` method may be used to ensure that all files within a given namespace are enums. ```php arch('app') ->expect('App\Enums') ->toBeEnums(); ``` ### `toBeIntBackedEnums()` The `toBeIntBackedEnums()` method may be used to ensure that all enums within a specified namespace are int-backed. ```php arch('app') ->expect('App\Enums') ->toBeIntBackedEnums(); ``` ### `toBeInterfaces()` The `toBeInterfaces()` method may be used to ensure that all files within a given namespace are interfaces. ```php arch('app') ->expect('App\Contracts') ->toBeInterfaces(); ``` ### `toBeInvokable()` The `toBeInvokable()` method may be used to ensure that all files within a given namespace are invokable. ```php arch('app') ->expect('App\Actions') ->toBeInvokable(); ``` ### `toBeTraits()` The `toBeTraits()` method may be used to ensure that all files within a given namespace are traits. ```php arch('app') ->expect('App\Concerns') ->toBeTraits(); ``` ### `toBeFinal()` The `toBeFinal()` method may be used to ensure that all classes within a given namespace are final. ```php arch('app') ->expect('App\ValueObjects') ->toBeFinal(); ``` Note that, typically this expectation is used in combination with the `classes()` modifier to ensure that all classes within a given namespace are final. ```php arch('app') ->expect('App') ->classes() ->toBeFinal(); ``` ### `toBeReadonly()` The `toBeReadonly()` method may be used to ensure that certain classes are immutable and cannot be modified at runtime. ```php arch('app') ->expect('App\ValueObjects') ->toBeReadonly(); ``` Note that, typically this expectation is used in combination with the `classes()` modifier to ensure that all classes within a given namespace are readonly. ```php arch('app') ->expect('App') ->classes() ->toBeReadonly(); ``` ### `toBeStringBackedEnums()` The `toBeStringBackedEnums()` method may be used to ensure that all enums within a specified namespace are string-backed. ```php arch('app') ->expect('App\Enums') ->toBeStringBackedEnums(); ``` ### `toBeUsed()` The `not` modifier, when combined with the `toBeUsed()` method, enables you to verify that certain classes or functions are not being utilized by your application. ```php arch('globals') ->expect(['dd', 'dump']) ->not->toBeUsed(); arch('facades') ->expect('Illuminate\Support\Facades') ->not->toBeUsed(); ``` ### `toBeUsedIn()` By combining the `not` modifier with the `toBeUsedIn()` method, you can restrict specific classes and functions from being used within a given namespace. ```php arch('globals') ->expect('request') ->not->toBeUsedIn('App\Domain'); arch('globals') ->expect('Illuminate\Http') ->not->toBeUsedIn('App\Domain'); ``` ### `toExtend()` The `toExtend()` method may be used to ensure that all classes within a given namespace extend a specific class. ```php arch('app') ->expect('App\Models') ->toExtend('Illuminate\Database\Eloquent\Model'); ``` ### `toExtendNothing()` The `toExtendNothing()` method may be used to ensure that all classes within a given namespace do not extend any class. ```php arch('app') ->expect('App\ValueObjects') ->toExtendNothing(); ``` ### `toImplement()` The `toImplement()` method may be used to ensure that all classes within a given namespace implement a specific interface. ```php arch('app') ->expect('App\Jobs') ->toImplement('Illuminate\Contracts\Queue\ShouldQueue'); ``` ### `toImplementNothing()` The `toImplementNothing()` method may be used to ensure that all classes within a given namespace do not implement any interface. ```php arch('app') ->expect('App\ValueObjects') ->toImplementNothing(); ``` ### `toHaveMethodsDocumented()` The `toHaveMethodsDocumented()` method may be used to ensure that all methods within a given namespace are documented. ```php arch('app') ->expect('App') ->toHaveMethodsDocumented(); ``` ### `toHavePropertiesDocumented()` The `toHavePropertiesDocumented()` method may be used to ensure that all properties within a given namespace are documented. ```php arch('app') ->expect('App') ->toHavePropertiesDocumented(); ``` ### `toHaveAttribute()` The `toHaveAttribute()` method may be used to ensure that a certain class has a specific attribute. ```php arch('app') ->expect('App\Console\Commands') ->toHaveAttribute('Symfony\Component\Console\Attribute\AsCommand'); ``` ### `toHaveFileSystemPermissions()` The `toHaveFileSystemPermissions()` method may be used to ensure that all files within a given namespace have specific file system permissions. ```php arch('app') ->expect('App') ->not->toHaveFileSystemPermissions('0777'); ``` ### `toHaveLineCountLessThan()` The `toHaveLineCountLessThan()` method may be used to ensure that all files within a given namespace have a line count less than a specified value. ```php arch('app') ->expect('App\Models') ->toHaveLineCountLessThan(100); ``` ### `toHaveMethod()` The `toHaveMethod()` method may be used to ensure that a certain class has a specific method. ```php arch('app') ->expect('App\Http\Controllers\HomeController') ->toHaveMethod('index'); ``` ### `toHaveMethods()` The `toHaveMethods()` method may be used to ensure that a certain class has specific methods. ```php arch('app') ->expect('App\Http\Controllers\HomeController') ->toHaveMethods(['index', 'show']); ``` ### `toHavePrivateMethodsBesides()` The `toHavePrivateMethodsBesides()` method may be used to ensure that a certain class does not have any private methods besides the specified ones. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHavePrivateMethodsBesides(['doPayment']); ``` ### `toHavePrivateMethods()` The `toHavePrivateMethods()` method may be used to ensure that a certain class does not have any private methods. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHavePrivateMethods(); ``` ### `toHaveProtectedMethodsBesides()` The `toHaveProtectedMethodsBesides()` method may be used to ensure that a certain class does not have any protected methods besides the specified ones. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHaveProtectedMethodsBesides(['doPayment']); ``` ### `toHaveProtectedMethods()` The `toHaveProtectedMethods()` method may be used to ensure that a certain class does not have any protected methods. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHaveProtectedMethods(); ``` ### `toHavePublicMethodsBesides()` The `toHavePublicMethodsBesides()` method may be used to ensure that a certain class does not have any public methods besides the specified ones. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHavePublicMethodsBesides(['charge', 'refund']); ``` ### `toHavePublicMethods()` The `toHavePublicMethods()` method may be used to ensure that a certain class does not have any public methods. ```php arch('app') ->expect('App\Services\PaymentService') ->not->toHavePublicMethods(); ``` ### `toHavePrefix()` The `toHavePrefix()` method may be used to ensure that all files within a given namespace have a specific prefix. ```php arch('app') ->expect('App\Helpers') ->not->toHavePrefix('Helper'); ``` ### `toHaveSuffix()` The `toHaveSuffix()` method may be used to ensure that all files within a given namespace have a specific suffix. ```php arch('app') ->expect('App\Http\Controllers') ->toHaveSuffix('Controller'); ``` ### `toHaveSuspiciousCharacters()` The `toHaveSuspiciousCharacters()` method may be used to help you identify potential suspicious characters in your code. ```php arch('app') ->expect('App\Http\Controllers') ->not->toHaveSuspiciousCharacters(); ``` This expectation requires the `intl` PHP extension. ### `toHaveConstructor()` This `toHaveConstructor()` method may be used to ensure that all files within a given namespace have a `__construct` method. ```php arch('app') ->expect('App\ValueObjects') ->toHaveConstructor(); ``` ### `toHaveDestructor()` This `toHaveDestructor()` method may be used to ensure that all files within a given namespace have a `__destruct` method. ```php arch('app') ->expect('App\ValueObjects') ->toHaveDestructor(); ``` ### `toOnlyImplement()` The `toOnlyImplement()` method may be used to ensure that certain classes are restricted to implementing specific interfaces. ```php arch('app') ->expect('App\Responses') ->toOnlyImplement('Illuminate\Contracts\Support\Responsable'); ``` ### `toOnlyUse()` The `toOnlyUse()` method may be used to guarantee that certain classes are restricted to utilizing specific functions or classes. For example, you may ensure your models are streamlined and solely dependent on the `Illuminate\Database` namespace, and not, for instance, dispatching queued jobs or events. ```php arch('models') ->expect('App\Models') ->toOnlyUse('Illuminate\Database'); ``` ### `toOnlyBeUsedIn()` The `toOnlyBeUsedIn()` method enables you to limit the usage of a specific class or set of classes to only particular parts of your application. For instance, you can use this method to confirm that your models are only used by your repositories and not by controllers or service providers. ```php arch('models') ->expect('App\Models') ->toOnlyBeUsedIn('App\Repositories'); ``` ### `toUse()` By combining the `not` modifier with the `toUse()` method, you can indicate that files within a given namespace should not use specific functions or classes. ```php arch('globals') ->expect('App\Domain') ->not->toUse('request'); arch('globals') ->expect('App\Domain') ->not->toUse('Illuminate\Http'); ``` ### `toUseStrictEquality()` The `toUseStrictEquality()` method may be used to ensure that all files within a given namespace use strict equality. In other words, the `===` operator is used instead of the `==` operator. ```php arch('models') ->expect('App') ->toUseStrictEquality(); ``` Or, if you rather want to ensure that all files within a given namespace do not use strict equality, you may use the `not` modifier. ```php arch('models') ->expect('App') ->not->toUseStrictEquality(); ``` ### `toUseTrait()` The `toUseTrait()` method may be used to ensure that all files within a given namespace use a specific trait. ```php arch('models') ->expect('App\Models') ->toUseTrait('Illuminate\Database\Eloquent\SoftDeletes'); ``` ### `toUseTraits()` The `toUseTraits()` method may be used to ensure that all files within a given namespace use specific traits. ```php arch('models') ->expect('App\Models') ->toUseTraits(['Illuminate\Database\Eloquent\SoftDeletes', 'App\Concerns\CustomTrait']); ``` ### `toUseNothing()` If you want to indicate that particular namespaces or classes should not have any dependencies, you can utilize the `toUseNothing()` method. ```php arch('value objects') ->expect('App\ValueObjects') ->toUseNothing(); ``` ### `toUseStrictTypes()` The `toUseStrictTypes()` method may be used to ensure that all files within a given namespace utilize strict types. ```php arch('app') ->expect('App') ->toUseStrictTypes(); ``` ## Presets Sometimes, writing arch expectations from scratch can be time-consuming, specifically when working on a new project, and you just want to ensure that the basic architectural rules are met. Presets are predefined sets of granular expectations that you can use to test your application's architecture.
- [`php`](#preset-php) - [`security`](#preset-security) - [`laravel`](#preset-laravel) - [`strict`](#preset-strict) - [`custom`](#preset-custom)
### `php` The `php` preset is a predefined set of expectations that can be used on any php project. It's not coupled with any framework or library. It avoids the usage of `die`, `var_dump`, and similar functions, and ensures you are not using deprecated PHP functions. ```php arch()->preset()->php(); ``` You may find all the expectations included in the `php` preset below in our [source code](https://github.com/pestphp/pest/blob/4.x/src/ArchPresets/Php.php). This preset requires the `intl` PHP extension. ### `security` The `security` preset is a predefined set of expectations that can be used on any php project. It's not coupled with any framework or library. It ensures you are not using code that could lead to security vulnerabilities, such as `eval`, `md5`, and similar functions. ```php arch()->preset()->security(); ``` You may find all the expectations included in the `security` preset below in our [source code](https://github.com/pestphp/pest/blob/3.x/src/ArchPresets/Security.php). ### `laravel` The `laravel` preset is a predefined set of expectations that can be used on [Laravel](https://laravel.com) projects. It ensures your project's structure is following the well-known Laravel conventions, such as controllers only having `index`, `show`, `create`, `store`, `edit`, `update`, `destroy` as public methods and are always suffixed with `Controller` and so on. ```php arch()->preset()->laravel(); ``` You may find all the expectations included in the `laravel` preset below in our [source code](https://github.com/pestphp/pest/blob/3.x/src/ArchPresets/Laravel.php). ### `strict` The `strict` preset is a predefined set of expectations that can be used on any php project. It's not coupled with any framework or library. It ensures you are using strict types in all your files, that all your classes are final, and more. ```php arch()->preset()->strict(); ``` You may find all the expectations included in the `strict` preset below in our [source code](https://github.com/pestphp/pest/blob/3.x/src/ArchPresets/Strict.php). ### `relaxed` The `relaxed` preset is a predefined set of expectations that can be used on any php project. It's not coupled with any framework or library. It is the opposite of the `strict` preset, ensuring you are not using strict types in all your files, that all your classes are not final, and more. ```php arch()->preset()->relaxed(); ``` You may find all the expectations included in the `relaxed` preset below in our [source code](https://github.com/pestphp/pest/blob/3.x/src/ArchPresets/Relaxed.php). ### `custom` Typically, you don't need to create a `custom` preset, as you can use the `arch()` method to write your granular expectations. However, if you want to create your own preset, you can use the `custom` method to define the preset. This may be useful if you have a set of expectations that you use frequently across multiple projects, or if you are a plugin author and want to provide a set of expectations for your users. ```php pest()->presets()->custom('ddd', function () { return [ expect('Infrastructure')->toOnlyBeUsedIn('Application'), expect('Domain')->toOnlyBeUsedIn('Application'), ]; }); ``` Within the `custom` method, you may have access to the application PSR-4 namespaces on the first argument of your closure's callback. ```php pest()->presets()->custom('silex', function (array $userNamespaces) { var_dump($userNamespaces); // array(1) { [0]=> string(3) "App" } return [ expect($userNamespaces)->toBeArray(), ]; }); ``` You can then use the `custom` preset by chaining the `preset()` method with the name of the custom preset. ```php arch()->preset()->silex(); ``` ## Wildcards Since Pest 3.8, you can pass wildcards to the `expect()` method to match code in multiple namespaces. For example, if you want to ensure all code within any `Traits` subdirectory contain traits, you can use the following: ```php arch() ->expect('App\*\Traits') // All code within any App\*\Traits namespace, e.g. App\Models\Traits, etc. ->toBeTraits(); arch() ->expect('App\*\*\Traits') // All code within any App\*\*\Traits namespace, e.g. App\A\B\Traits, App\C\D\Traits, etc. ->toBeTraits(); ``` ## Modifiers Sometimes, you may want to apply the given expectation but excluding certain types of files, or ignoring certain classes, functions, or specific lines of code. For that, you may use the following methods:
- [`ignoring()`](#modifier-ignoring) - [`classes()`](#modifier-classes) - [`enums()`](#modifier-enums) - [`interfaces()`](#modifier-interfaces) - [`traits()`](#modifier-traits) - [`extending()`](#modifier-extending) - [`implementing()`](#modifier-implementing) - [`using()`](#modifier-using) - [`abstracts()`](#modifier-abstracts)
### `ignoring()` When defining your architecture rules, you can use the `ignoring()` method to exclude certain namespaces or classes that would otherwise be included in the rule definition. ```php arch() ->preset() ->php() ->ignoring('die'); arch() ->expect('Illuminate\Support\Facades') ->not->toBeUsed() ->ignoring('App\Providers'); ``` In some cases, certain components may not be regarded as "dependencies" as they are part of the native PHP library. To customize the definition of "native" code and exclude it during testing, Pest allows you to specify what to ignore. For example, if you do not want to consider Laravel a "dependency", you can use the `arch()` method inside the `beforeEach()` function to disregard any code within the "Illuminate" namespace. This approach allows you to focus only on the actual dependencies of your application. ```php // tests/Pest.php pest()->beforeEach(function () { $this->arch()->ignore([ 'Illuminate', ])->ignoreGlobalFunctions(); }); ``` ### `classes()` The `classes()` modifier allows you to restrict the expectation to only classes. ```php arch('app') ->expect('App') ->classes() ->toBeFinal(); ``` ### `enums()` The `enums()` modifier allows you to restrict the expectation to only enums. ```php arch('app') ->expect('App\Models') ->enums() ->toOnlyBeUsedIn('App\Models'); ``` ### `interfaces()` The `interfaces()` modifier allows you to restrict the expectation to only interfaces. ```php arch('app') ->expect('App') ->interfaces() ->toExtend('App\Contracts\Contract'); ``` ### `traits()` The `traits()` modifier allows you to restrict the expectation to only traits. ```php arch('app') ->expect('App') ->traits() ->toExtend('App\Traits\Trait'); ``` ### `extending()` The `extending()` modifier allows you to restrict the expectation to only classes or interfaces that extend the given class. ```php arch('app') ->expect('App') ->extending(Model::class) ->toUseTrait(HasFactory::class); ``` ### `implementing()` The `implementing()` modifier allows you to restrict the expectation to only classes that implement the given interface. ```php arch('app') ->expect('App') ->implementing(ShouldQueue::class) ->toUseTrait(Dispatchable::class); ``` ### `using()` The `using()` modifier allows you to restrict the expectation to only classes that use the given trait. ```php arch('app') ->expect('App') ->using(HasFactory::class) ->toExtend(Model::class); ``` ### `abstracts()` The `abstracts()` modifier allows you to restrict the expectation to only abstract classes. ```php arch('app') ->expect('App') ->abstracts() ->toImplement(JsonSerializable::class); ``` --- In this section, you have learned how to perform architectural testing, ensuring that your application or library's architecture meets a specified set of architectural requirements. Next, have you ever wondered how to test the performance of your code? Let's explore [Stress Testing](/docs/stress-testing).