Pest Architecture Testing in Laravel | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Pest Architecture Testing: Enforcing Domain Boundaries in a Laravel Codebase        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)

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

  #pest   #laravel   #testing   #architecture   #ddd  

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

     24 Aug 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Architecture Tests Belong in Your CI Pipeline  ](#why-architecture-tests-belong-in-your-ci-pipeline)
2. [  02   Setting Up the Architecture Plugin  ](#setting-up-the-architecture-plugin)
3. [  03   Enforcing Domain Purity  ](#enforcing-domain-purity)
4. [  04   Naming Convention Rules  ](#naming-convention-rules)
5. [  05   Preventing Upward Dependencies  ](#preventing-upward-dependencies)
6. [  06   Combining with arch Presets  ](#combining-with-codearchcode-presets)
7. [  07   Ignoring Specific Classes  ](#ignoring-specific-classes)
8. [  08   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.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fpest-architecture-testing-enforcing-domain-boundaries-in-a-laravel-codebase&text=Pest+Architecture+Testing%3A+Enforcing+Domain+Boundaries+in+a+Laravel+Codebase) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fpest-architecture-testing-enforcing-domain-boundaries-in-a-laravel-codebase) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  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. 

      Q02  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. 

      Q03  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. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation](https://cdn.msaied.com/602/fcffaaa5442f84486d6059eaa4106d26.png) laravel queues reliability 

### Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation

Beyond basic queue workers: learn how to implement backpressure signals, dead-letter queues, and graceful degr...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-queues-at-scale-backpressure-dead-letter-queues-and-graceful-degradation) [ ![Mask Query Bindings in Laravel Exception Messages](https://cdn.msaied.com/603/3011313796d00cd5c4e1ead00e1e9ba1.png) Laravel Security QueryException 

### Mask Query Bindings in Laravel Exception Messages

Laravel 13.27 adds a per-connection option to prevent bound query values from appearing in QueryException mess...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 27 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/mask-query-bindings-in-laravel-exception-messages) [ ![whereBinary(): How to Run Case-Sensitive MySQL Queries in Laravel 13.27](https://cdn.msaied.com/600/0c7655400b43d3b85d1d1e9d0f4c8094.png) Laravel MySQL Query Builder 

### whereBinary(): How to Run Case-Sensitive MySQL Queries in Laravel 13.27

Laravel 13.27 adds whereBinary(), orWhereBinary(), whereNotBinary(), and orWhereNotBinary() — clean query-buil...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 26 Aug 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/wherebinary-how-to-run-case-sensitive-mysql-queries-in-laravel-1327) 

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

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
