Pest Architecture Testing in Laravel | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Pest Architecture Testing: Enforcing Domain Boundaries in a Laravel Codebase

 Pest Architecture Testing: Enforcing Domain Boundaries in a Laravel Codebase
=============================================================================

 Pest's architecture plugin lets you write executable rules that prevent domain leakage, enforce naming conventions, and keep your Laravel bounded contexts honest — all inside your normal test suite.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 24 Aug 2026 · Updated 24 Aug 2026 · 3 min read

ShareCopy linkCopied

 ![Pest Architecture Testing: Enforcing Domain Boundaries in a Laravel Codebase](https://cdn.msaied.com/586/d7ec30575e05c87e236fa8000b60175d.png) 

  On this page +1. [Why Architecture Tests Belong in Your CI Pipeline](#why-architecture-tests-belong-in-your-ci-pipeline)
2. [Setting Up the Architecture Plugin](#setting-up-the-architecture-plugin)
3. [Enforcing Domain Purity](#enforcing-domain-purity)
4. [Naming Convention Rules](#naming-convention-rules)
5. [Preventing Upward Dependencies](#preventing-upward-dependencies)
6. [Combining with arch Presets](#combining-with-codearchcode-presets)
7. [Ignoring Specific Classes](#ignoring-specific-classes)
8. [Key Takeaways](#key-takeaways)

 Why Architecture Tests Belong in Your CI Pipeline
-------------------------------------------------

Code reviews catch style drift. Architecture tests catch structural rot. When a junior dev imports an Eloquent model directly into a domain action, no linter fires — but your architecture test will. Pest's `arch()` API turns architectural decisions into first-class, version-controlled assertions that run on every push.

This article focuses on practical, opinionated rules for a Laravel codebase that follows a modular or DDD-lite structure.

---

Setting Up the Architecture Plugin
----------------------------------

Pest ships the architecture API out of the box from v2.x onward. No extra package is needed.

```bash
composer require pestphp/pest --dev

```

Create a dedicated file so the rules stay separate from feature tests:

```
tests/
  Architecture/
    DomainTest.php
    InfrastructureTest.php

```

---

Enforcing Domain Purity
-----------------------

The core rule: domain classes must never depend on the framework's infrastructure layer.

```php
// tests/Architecture/DomainTest.php

arch('domain actions do not depend on Eloquent')
    ->expect('App\Domain')
    ->not->toUse('Illuminate\Database\Eloquent');

arch('domain value objects are readonly')
    ->expect('App\Domain\ValueObjects')
    ->toBeReadonly();

arch('domain DTOs are final')
    ->expect('App\Domain\DataTransferObjects')
    ->toBeFinal();

```

These three rules alone prevent the most common leakage patterns: an action that reaches for `User::find()`, a value object mutated after construction, and a DTO subclassed into something unrecognisable.

---

Naming Convention Rules
-----------------------

Consistency in naming is load-bearing in large teams. Pest can assert it:

```php
arch('actions are suffixed correctly')
    ->expect('App\Domain\Actions')
    ->toHaveSuffix('Action');

arch('jobs live in the right namespace')
    ->expect('App\Jobs')
    ->toImplement(\Illuminate\Contracts\Queue\ShouldQueue::class);

arch('repositories extend the base repository')
    ->expect('App\Infrastructure\Repositories')
    ->toExtend('App\Infrastructure\Repositories\BaseRepository');

```

The `toHaveSuffix` / `toHavePrefix` matchers are simple but eliminate entire categories of "where does this class live?" confusion.

---

Preventing Upward Dependencies
------------------------------

In a layered architecture, infrastructure must not bleed into the domain, and the domain must not know about HTTP concerns.

```php
arch('domain layer is unaware of HTTP')
    ->expect('App\Domain')
    ->not->toUse([
        'Illuminate\Http\Request',
        'Illuminate\Http\Response',
        'Illuminate\Routing\Controller',
    ]);

arch('infrastructure does not import application services directly')
    ->expect('App\Infrastructure')
    ->not->toUse('App\Application\Services');

```

---

Combining with `arch` Presets
-----------------------------

Pest ships a handful of opinionated presets that cover common Laravel conventions:

```php
arch()->preset()->laravel();
arch()->preset()->strict();

```

`strict()` enforces no `dd()`, no `var_dump()`, and strict types across all files. `laravel()` validates that controllers, models, and jobs follow framework conventions. Layer your own rules on top rather than replacing these.

---

Ignoring Specific Classes
-------------------------

Sometimes a rule has a legitimate exception. Use `ignoring()` rather than deleting the rule:

```php
arch('domain actions do not depend on Eloquent')
    ->expect('App\Domain')
    ->not->toUse('Illuminate\Database\Eloquent')
    ->ignoring('App\Domain\Actions\SeedDemoDataAction');

```

The exception is explicit, documented, and reviewable in git history.

---

Key Takeaways
-------------

- Architecture tests are executable documentation — they fail loudly when structure drifts.
- `arch()` rules run in milliseconds; add them to the same `pest` command as unit tests.
- Start with three rules: no Eloquent in the domain, readonly value objects, final DTOs.
- Use `ignoring()` for deliberate exceptions rather than weakening the rule.
- Combine Pest presets (`laravel()`, `strict()`) with project-specific rules for layered coverage.
- Commit architecture tests alongside the architectural decision record (ADR) that motivated them.

- [pest](https://www.msaied.com/public/articles?search=pest)
- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [testing](https://www.msaied.com/public/articles?search=testing)
- [architecture](https://www.msaied.com/public/articles?search=architecture)
- [ddd](https://www.msaied.com/public/articles?search=ddd)

 Frequently asked questions 
---------------------------

  Do architecture tests slow down the test suite significantly?No. Pest's arch() assertions perform static analysis on class metadata rather than executing application code, so a full set of architecture rules typically adds under a second to the suite.

   Can I run architecture tests separately from unit and feature tests?Yes. Place them in a dedicated directory (e.g. tests/Architecture) and use Pest's --filter or a separate phpunit group to run them independently in CI if needed.

   What happens when a rule is violated — does it show which class caused the failure?Pest reports the exact fully-qualified class name that violated the rule, making it straightforward to locate and fix the offending dependency.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleFrankenPHP, OPcache JIT, and Preloading: Squeezing Real Throughput from Laravel](https://www.msaied.com/public/articles/frankenphp-opcache-jit-and-preloading-squeezing-real-throughput-from-laravel-3) [Next articleLivewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://www.msaied.com/public/articles/livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration-4)  

   On this page
-------------

1. [Why Architecture Tests Belong in Your CI Pipeline](#why-architecture-tests-belong-in-your-ci-pipeline)
2. [Setting Up the Architecture Plugin](#setting-up-the-architecture-plugin)
3. [Enforcing Domain Purity](#enforcing-domain-purity)
4. [Naming Convention Rules](#naming-convention-rules)
5. [Preventing Upward Dependencies](#preventing-upward-dependencies)
6. [Combining with arch Presets](#combining-with-codearchcode-presets)
7. [Ignoring Specific Classes](#ignoring-specific-classes)
8. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
