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

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

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

  [ home ](https://msaied.com)    [ articles ](https://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%2Fmsaied.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%2Fmsaied.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://msaied.com/articles) 

 [ ![FrankenPHP, OPcache JIT, and Preloading: Squeezing Real Throughput from Laravel](https://cdn.msaied.com/585/7dc4b3832f33e2c630172d5b5dd24ac1.png) laravel frankenphp performance 

### FrankenPHP, OPcache JIT, and Preloading: Squeezing Real Throughput from Laravel

A practical guide to deploying Laravel under FrankenPHP with OPcache JIT and preloading enabled — covering wor...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://msaied.com/articles/frankenphp-opcache-jit-and-preloading-squeezing-real-throughput-from-laravel-3) [ ![Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts](https://cdn.msaied.com/584/3ffe135721c65ab3f9b40401dc3c41de.png) laravel ai llm 

### Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts

Learn how to stream LLM responses in Laravel, enforce token budgets, and lock structured output to typed PHP c...

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

 23 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/streaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts) [ ![Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules](https://cdn.msaied.com/583/7301900aac0f2ec5d1347df11ce92188.png) laravel php8.3 enums 

### Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules

Go beyond basic enum casting. Learn how to wire PHP 8.3 backed enums into Eloquent, route model binding, and c...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://msaied.com/articles/typed-php-83-enums-as-eloquent-casts-route-parameters-and-validation-rules) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
