---
title: Architecture testing with Laravel Pest
published: "2024-04-30"
updated: "2024-11-08"
publisher: Honeybadger
author: Ashley Allen
category: PHP articles
tags:
  - PHP
  - Laravel
  - Testing
description: Enforcing consistency across a codebase is tedious. Jump into this article to learn how to test your architecture with Laravel Pest.
url: "https://www.honeybadger.io/blog/laravel-pest-architecture-testing/"
---

Ensuring that a growing codebase follows best practices and does not deviate from the standards is essential for any project. However, this can typically only be enforced manually by code reviews and other similar processes. As with any other manual task, this can be time-consuming, tedious, and error-prone. That's where architecture testing comes in.

Architecture testing allows you to automatically enforce code standards such as naming conventions, method usage, directory structure, and more.

In this article, we're going to learn about architecture testing and the benefits of using it. We'll then look at how to write architecture tests for our Laravel applications using Laravel Pest, the popular PHP testing framework. By the end, you'll be confident in writing architecture tests for your Laravel applications.

## What is Laravel Pest?

Pest is an open-source PHP testing framework built on top of PHPUnit. At the time of writing, it has over 9.4 million downloads and 7.9k stars on GitHub. It uses a syntax and approach similar to JavaScript's Jest and Ruby's RSpec. If you've used either of these, you should feel at home using Pest for [PHP tests](https://www.honeybadger.io/blog/php-testing/).

For example, imagine you have a simple `add` function that adds two numbers together, and you want to ensure that it returns the correct value. You might write a test in Pest like this:

```php
test('add function returns the correct value') ->expect(add(1, 2)) ->toBe(3);
```

As we can see, the tests use a fluent syntax that allows us to chain methods together. This results in tests that are simple, easy to read, and understand.

A benefit to using Pest for testing your Laravel applications is that it offers several features that you can use to improve your testing experience. These include:

- **Architecture testing** - This allows you to enforce code standards automatically, such as naming conventions, method usage, and directory structure. (That's what we're covering in this article.)
- **Parallel testing** - This allows you to run your tests in parallel, which can help to speed up your test suite.
- **Snapshot testing** - This allows you to test that values are the same as when the snapshot was created. Snapshot testing can help when testing API responses or when performing large refactors, and you want to ensure the output hasn't changed.
- **Test profiling** - This allows you to see how long each test takes to run and can be useful for identifying slow tests that you may want to refactor.
- **Watch mode** - This allows you to automatically re-run your tests whenever a file changes in your project.
- **Code coverage** - This allows you to see how much of your code is exercised by your tests to know which parts of your codebase need more tests. It can also provide insight into the type coverage of your codebase to identify any methods that aren't using return types and type hints.

Since Pest is built on top of PHPUnit, you can also run your existing PHPUnit tests using Pest. That means that you don't need to worry about migrating your current tests to the Pest syntax to be able to use it and start benefiting from the other features it offers.

## What is architecture testing?

Architecture testing differs from the traditional tests you might normally write for your projects. Whereas your traditional tests test the functionality of your application, architecture tests test the structure of your application. They allow you to enforce standards and consistency in your projects.

Depending on the type of project you're working on, you may have a set of standards or guidelines set by your employer, client, or yourself. For example, you might want to enforce things such as:

- Strict type-checking is used in all files.
- The `app/Interfaces` directory only contains interfaces.
- All classes in the `app/Services` directory are suffixed with `Service` (e.g., `UserService`, `BillingService`, etc.).
- All models in the `app/Models` directory extend the `Illuminate\Database\Eloquent\Model` class.
- Controllers aren't used anywhere apart from the routes files in the application.
- Only traits exist in the `app/Traits` directory.

Generally, you should enforce these things manually via code reviews and other similar processes. With architecture testing, you can enforce these standards automatically and know for sure that they're followed.

For example, take this simple test that enforces that all interfaces exist within the `App\Interfaces` namespace:

```php
test('interfaces namespace only contains interfaces') ->expect('App\Interfaces') ->toBeInterfaces();
```

The above test can be run like any other Pest test. The test will pass if all the files in the `App\Interfaces` namespace are interfaces. However, the test will fail if any of the files in this namespace aren't interfaces.

## Benefits of architecture testing

Now that we have a basic understanding of architecture testing let's look at its benefits in your projects.

### Reduce code review time

As we've already mentioned, architecture testing is an excellent way of automating the process of enforcing standards in your codebase.

By using an automated architecture testing strategy, you can provide instant feedback that doesn't involve other developers. This means that a developer can spot and fix architecture issues before other developers are involved in the approval process. This speeds up the development process and reduces the time the other developer needs to spend reviewing the code and looking for architectural issues.

### Improve code quality and consistency

Enforcing standards and consistency early on in a project can help to improve the overall code quality. It helps implement a sensible set of rules that developers can follow to ensure they write code that other developers can understand.

Every developer has their own way of writing code. I'm sure you'll agree that once you've worked on a project for a long enough time, you can usually tell who wrote a particular piece of code by the variable and method names or how the classes are structured. Minor differences like these aren't typically a big deal as long as they follow the general standards of the project that the other developers are also following.

These types of differences can cause consistency issues and make it difficult for other developers to understand and work on the codebase. Although inconsistencies such as these should have been caught during a manual code review process, they may not have been.

For example, if you want to enforce that there are no interfaces outside of the `app/Interfaces` directory, you could write some Pest tests like this:

```php
test('services namespace only includes classes') ->expect('App\Services') ->toBeClasses(); test('interfaces namespace only contains interfaces') ->expect('App\Interfaces') ->toBeInterfaces();
```

Now, if there's an interface in the `App\Services` namespace, the Pest test will fail and must be fixed before merging the code into the main branch. As a result, it can help ensure that every developer uses the same approach.

## Get started with architecture testing

Now that we've learned about architecture testing and its benefits in your projects, we'll walk through installing Pest and writing our first architecture tests.

![Laravel Pest testing documentation](https://www-files.honeybadger.io/posts/laravel-pest-architecture-testing/pest-architecture-testing.png)

You'll need to install Pest in your Laravel project using Composer. You can do this by running the following command in your project's root directory:

```text
composer require pestphp/pest-plugin-laravel --dev
```

Next, we'll look at typical examples of architecture tests you might want to add to your Laravel applications. We'll begin by creating a simple test that asserts that the `App\Models` namespace contains only classes and that each class extends the `Illuminate\Database\Eloquent\Model` class.

Where you place your architecture tests is a personal preference, but I like to put mine in a `tests/Architecture` directory. Doing this allows me to keep my architecture tests separate from my feature and unit tests. For our tests inside that directory to be detected and run, we'll need to update the `phpunit.xml` file to include it. We can do this by adding a new `Architecture` test suite to the `<testsuites>` section:

```xml
<testsuites> ... <testsuite name="Architecture"> <directory>tests/Architecture</directory> </testsuite> </testsuites>
```

I like to structure my architecture tests in a way that's similar to my application code's directory structure. By doing this, it makes it easier to find the tests that are related to a particular part of the project. For example, I place my architecture tests for models (found in `app/Models`) in a `tests/Architecture/ModelsTest.php` test file. I put my controller architecture tests (in `app/Http/Controllers`) in a `tests/Architecture/Http/ControllersTest.php` test file. And so on.

Next, create a `tests/Architecture/ModelsTest.php` file and add the following code to it:

```php
use Illuminate\Database\Eloquent\Model; test('models extends base model') ->expect('App\Models') ->toExtend(Model::class);
```

We can now run this test using the following command:

```text
php artisan test
```

If you configured everything correctly, you should see an output like this when you run the test:

```text
PASS  Tests\Architecture\ModelsTest ✓ models extends base model                                                      0.06s Tests:    1 passed (2 assertions) Duration: 0.14s
```

Now, let's purposely break the tests to ensure your test is working correctly and detecting any architectural issues. To do this, let's add an empty PHP class that doesn't extend the `Illuminate\Database\Eloquent\Model` class. The class may look something like this:

```php
namespace App\Models; class UserService { }
```

If you re-run the tests, the tests should fail, and you will get an output that looks like this:

```text
FAIL  Tests\Architecture\ModelsTest ⨯ models extends base model                                                      0.07s ──────────────────────────────────────────────────────────---------------------------- FAILED  Tests\Architecture\ModelsTest > models          ArchExpectationFailedException Expecting 'app/Models/UserService.php' to extend 'Illuminate\Database\Eloquent\Model'. at app/Models/UserService.php:5 1▕ <?php 2▕ 3▕ namespace App\Models; 4▕ ➜   5▕ class UserService 6▕ { 7▕ 8▕ } 9▕ 1   app/Models/UserService.php:5 Tests:    1 failed (2 assertions) Duration: 0.18s
```

## Common examples of architecture tests

Now that we've looked at how to install Pest and write our first architecture tests let's look at some common examples of architecture tests you might want to add to your Laravel applications.

Here are some of the common methods that you'll likely be using in your tests:

- `toBeAbstract` - Assert the classes in the given namespace are abstract (using the keyword `abstract`).
- `toBeClasses` - Assert the files in the given namespace are PHP classes.
- `toBeEnums` - Assert the files in the given namespace are PHP enums.
- `toBeInterfaces` - Assert the files in the given namespace are PHP interfaces.
- `toBeTraits` - Assert the files in the given namespace are PHP traits.
- `toBeInvokable` - Assert the classes in the given namespace are invokable (meaning they have an `__invoke` method).
- `toBeFinal` - Assert the classes in the given namespace are final (using the keyword `final`).
- `toBeReadonly` - Assert the classes in the given namespace are readonly (using the keyword `readonly`).
- `toExtend` - Assert the classes in the given namespace extend the given class.
- `toImplement` - Assert the classes in the given namespace implement the given interface.
- `toHaveMethod` - Assert the classes in the given namespace have the given method.
- `toHavePrefix` - Assert the classes in the given namespace start with the given string in their classnames.
- `toHaveSuffix` - Assert the classes in the given namespace end with the given string in their classnames.
- `toUseStrictTypes` - Assert the files in the given namespace use strict types (using the keywords `declare(strict_types=1)`).

You can check out the [Pest documentation](https://pestphp.com/docs/arch-testing) for a complete list of available assertions.

Now, let's look at some common tests you may want to write. This is not an exhaustive list, but it should provide an insight into the different assertions you might want to use. You can also use these as a starting point for your own tests and modify them to suit your needs.

### Tests for commands

You may want to write a test for your Artisan commands to assert that the `App\Console\Commands` namespace only contains valid command classes. To do this, you may want to write a test like this:

```php
declare(strict_types=1); use Illuminate\Console\Command; test('commands') ->expect('App\Console\Commands') ->toBeClasses() ->toUseStrictTypes() ->toExtend(Command::class) ->toHaveMethod('handle');
```

In this test, we're defining that we only expect classes in the `App\Console\Commands` namespace. We're then asserting that each class uses strict types (using `declare(strict_types=1)`), extends the `Illuminate\Console\Command` class, and has a `handle` method.

If we were to add any files in this namespace that don't meet these requirements (such as adding a class that doesn't extend the `Illuminate\Console\Command` class), the test would fail.

### Tests for controllers

You may also want to write a test to assert that your controllers follow specific standards. Let's take a look at an example test you could write, and then we'll discuss what we're doing:

```php
declare(strict_types=1); use Illuminate\Routing\Controller; test('controllers') ->expect('App\Http\Controllers') ->toBeClasses() ->toUseStrictTypes() ->toBeFinal() ->classes() ->toExtend(Controller::class);
```

In the test above, we assert that every PHP file in the `App\Http\Controllers` namespace is a class. We then assert that each of the classes uses strict types (using `declare(strict_types=1)`) and are final (using the `final` keyword). Finally, we assert that each class extends the `Illuminate\Routing\Controller` class.

If we were to add any other classes in this namespace that don't meet these requirements (such as adding a class that doesn't extend the `Illuminate\Routing\Controller` class), the test would fail.

### Tests for interfaces

You may also want to write a test to assert that your project's `App\Interfaces` namespace only contains interfaces. To do this, you may want to write a test like this:

```php
declare(strict_types=1); test('interfaces') ->expect('App\Interfaces') ->toBeInterfaces();
```

If we were to add any files in this namespace that don't meet these requirements (such as adding a class or trait), the test would fail.

### Tests for global functions

As well as testing that PHP files follow specific standards, we can also test that we don't use certain PHP global functions in our codebase. This can be a handy way to ensure that we don't accidentally leave debugging statements (such as `dd()`) in the code, which could cause issues in production.

You may want to write a test like this:

```php
declare(strict_types=1); test('globals') ->expect(['dd', 'ddd', 'die', 'dump', 'ray', 'sleep']) ->toBeUsedInNothing();
```

In the test above, we assert that the functions `dd`, `ddd`, `die`, `dump`, `ray`, and `sleep` aren't used anywhere in the application. If it finds one of them, the test will fail.

### Tests for jobs

Another helpful place you may want to write some architecture tests for is your job classes. You may wish to assert that all of your job classes implement the `Illuminate\Contracts\Queue\ShouldQueue` interface and have a `handle` method. To do this, you may want to write a test like this:

```php
declare(strict_types=1); use Illuminate\Contracts\Queue\ShouldQueue; test('jobs') ->expect('App\Jobs') ->toBeClasses() ->toImplement(ShouldQueue::class) ->toHaveMethod('handle');
```

In the test above, we assert that the `App\Jobs` namespace only includes classes that implement the `Illuminate\Contracts\Queue\ShouldQueue` interface and have a `handle` method. If we were to add an invalid file (such as a class that didn't implement the interface), the test would fail.

### Tests for models

Let's imagine that we have an `app/Models` directory to demonstrate how we might use the 'ignoring' modifier. Within this directory, we have a `Traits` directory (using the `App\Models\Traits` namespace). Imagine that this directory contains traits used by our models and nowhere else in the codebase.

We may want to write two tests that look like this:

```php
test('models') ->expect('App\Models') ->toBeClasses() ->ignoring('App\Models\Traits'); test('models extends base model') ->expect('App\Models') ->toExtend(Model::class) ->ignoring('App\Models\Traits');
```

In the first test (named `models`), we assert that every PHP file within the `App\Models` namespace is a class. We then use the `ignoring` modifier to ignore the `App\Models\Traits` namespace. That means that this test will ignore any files in this namespace.

In the second test (named `models extends base model`), we're asserting that every class (except those in the `App\Models\Traits` namespace) extends the `Illuminate\Database\Eloquent\Model` class, thus making it a valid model class.

## Improving your code with architecture testing

In this article, we've learned about what architecture testing is and the benefits of using it. We've also looked at how to write architecture tests for our applications using Laravel Pest.

Now that you're ready to start writing architecture tests for your Laravel applications, you'll soon see how they lead to an improved codebase. Over time, your tests will lead to you and your team making better decisions, and the value of the investment you made in architecture tests will compound. Happy testing!

Want more beyond testing? Check out how [Honeybadger supports Laravel](https://www.honeybadger.io/for/laravel/).

---

## Try Honeybadger for FREE

Intelligent logging, error tracking, and Just Enough APM™ in one dev-friendly platform. Find and fix problems before users notice.

[Start free trial](https://app.honeybadger.io/users/sign_up)

[See plans and pricing](https://www.honeybadger.io/plans/)
