This is an archived post from my previous blog. Some content may be outdated. FYI!


Introduction

Good day! In this article, we’ll take a look at how I personally structure a test suite of a Laravel project. We’ll discuss what goes where in terms of Integration/Feature Tests and Unit Tests, what exactly to test, and how I go about testing them. Note that what I’m about to share is my personal preference that I have developed throughout my usage of Laravel since 2017, so do take caution on whether following my tips is a good fit for your project or not.

It’ll tremendously help if you’ve written tests on your own already before reading this article, or at least have some familiarity with Laravel’s testing facilities.

With that out of the way, let’s get started!

Integration/Feature Tests

Out of the box, Laravel gives us the following structure within the tests folder:

tests
├── CreatesApplication.php
├── TestCase.php
├── Feature
│   └── ExampleTest.php
└── Unit
    └── ExampleTest.php

We’ll take a look at the tests/Feature directory first. Now feature tests, or more commonly known as integration tests, tests a specific feature of your application wherein multiple parts are working together. Wikipedia says that:

Integration testing is the phase in software testing in which individual software modules are combined and tested as a group.

In a nutshell, you’d want to write an integration test for each feature of your application that you’d want to implement. Say you are building a library management system. You’d want to test what you can do within that system, like the following:

tests/Feature
├── BrowseBooksTest.php
├── CreatePersonalCatalogTest.php
└── LoanBookTest.php

I suggest keep building your integration test suite within the tests/Feature directory until it starts to feel icky. Then it might be a good idea to create subdirectories to get a finer structure for your integration tests.

tests/Feature
├── Books
│   ├── ArchiveBookTest.php
│   ├── BrowseBookTest.php
│   └── ImportBookTest.php
├── Catalogs
│   ├── ArchiveCatalogTest.php
│   └── CreatePersonalCatalogTest.php
└── Loans
    ├── LoanBookTest.php
    └── LoanMagazineTest.php

At this point, you’ll have a test suite with clear distinctions for each domain wherein you can easily distinguish the separation of concerns.

Unit Tests

Whereas integration tests cover the ““playing nice”” aspects of your components, unit tests focus solely on the standalone, small parts. Wikipedia defines it as:

… unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.

To give a more concrete example, let’s continue with the library management system. Consider the following:

class LoanController
{
    public function store(Request $request, Book $book, LoanManager $manager)
    {
        $request->validate(/** ... */);

        $manager->loan($book)
                ->to($request->user());

        return back();
    }
}

The LoanManager class could’ve been implemented in many ways, but that’s not the point of this example. The LoanManager class contains business logic, in which the rules that govern the particular domain are implemented in code. This is a good candidate for unit testing because it works independently from the rest of the system. An example unit test for the LoanManager class might test the logic for whether a Book is available for loaning, or if a user is eligible to loan a book at all.

Now how do I structure unit tests? Simple: I just mimic the app/ directory. Here’s how my unit tests look like so far for this website’s simple blogging system:

tests/Unit
├── Helpers
│   └── CreateAdminUserTest.php
└── Models
    ├── ArticlesTest.php
    ├── ProjectTest.php
    ├── RolesTest.php
    ├── TagsTest.php
    └── UserTest.php

If I ever implement a payment system down the line, I’d probably put the implementation classes in app/Billing. Or if I’d implement a custom social media integration, that might go to app/Social etc. The point is that these kind of custom logic define the business logic for a system, and the implementation details are a good candidate for unit tests.

Testing Eloquent Models?

This is a huge debate within the Laravel community. Laravel’s Eloquent, which is an implementation of the Active Record pattern, doesn’t really lend itself nicely to decoupling from its data store. Which makes it hard to stub out and mock its dependencies when business logic is contained within an Eloquent model (which is also why I advocate creating service classes such as the LoanManager class above to decouple your business logic from the framework).

In any case, presentation logic and/or relationships are still parts of your business logic, and I personally find it tedious and unnecessary to write stubs or mock out the database connection when testing these. So my personal recommendation: if what you are testing can be defined as a ““single unit””, hitting the database is not really that bad.

Always favor functionality over purity.

A Note On Comments

I tend to look at my test suite as a documentation or specification for my application. Though repetitive comments (i.e. those that describe what is written in code) is a big no-no when writing implementation code, I find that telling a story alongside the test code is a nice way to keep your test suite skimmable without the reader losing context.

Here’s a short test from on of my projects:

/** @test */
public function the_edit_page_for_a_project_is_accessible_only_by_admins()
{
    // Given an admin and a project...
    $admin = create_admin_user();
    $project = factory(Project::class)->create();

    // When visiting that project's edit page...

    // An unauthenticated user is not allowed access...
    $this->get(route('admin.projects.edit', ['project' => $project->slug]))
        ->assertRedirect('/admin/login');

    // While the admin can.
    $this->actingAs($admin)
        ->get(route('admin.projects.edit', ['project' => $project->slug]))
        ->assertOk();
}

See how the method name and the comments tell a clear story on what is happening? So I humbly suggest: treat your tests as documentation and don’t cut yourself short on readability. Be generous!

Conclusion

In this article, we’ve taken a look on how I structure my Laravel test suites. We discussed the different type of tests (Integration and Unit) that the default Laravel directory structure imposes, and how I personally tackle the structure when writing my own tests. We’ve also taken a quick peek on testing Eloquent models, and how I made my case on writing comments as one is making the tests themselves.


Got any feedback or suggestions? Feel free to send me an email or a tweet.
Ciao!