Lowering the time cost of testing existing Laravel applications

Main Thread July 18, 2019 • 5 min read

In my guide to start testing your Laravel applications I mentioned two barriers to testing: time and skill.

In this post, I want to address time. ⏱

I'm someone who values time over anything else. From my perspective, time is the only thing I'll never have more of. It annoys me when something takes more time than I think or longer than I want.

Lately I've been adding tests for not only my own applications, but also pairing with others to write test for their existing applications.

This is a slow process. Before getting started with testing there's a lot of set up.

You need to:

  • Create each of the test classes for your various components.
  • Create factories for your models.
  • Define the model data and relationships.
  • Configure test environment variables.
  • Setup a test database.

While individually these are simple tasks, together they create a real drag.

The truth is many Laravel applications don't have tests for a reason. Maybe you don't have buy-in from the boss. Maybe no one else on your team cares about testing. Maybe you're learning.

Whatever the reason, something has prevented you from testing. Now you're finally ready to get started, and you’re faced with the tedious task of spending hours setting things up.

The real kicker is, after all this setup you still haven't written a test. There's nothing to immediately show for all your time and effort.

It sucks! I know we all want to test our Laravel applications. But time is a real barrier. So let's lower it.

Making it with artisan

Of course Laravel offers artisan commands to create some testing components.

You can create an HTTP Test with:

php artisan make:test Some/Http/ExampleTest

And you can create a unit test with:

php artisan make:test --unit ExampleTest

If you’re using Laravel Dusk, you can create a Dusk test with:

php artisan dusk:make ExampleTest

Each of these commands generates the respective test class with an example test case.

To generate test data for your models, you can create factories with:

php artisan make:factory SomeModelFactory

To get a little more out of this command, set the --model to specify which model the factory creates.

php artisan make:factory --model=SomeModel SomeModelFactory

These commands generate the file, any necessary folder structure, and what I call the class stub. It's really just skeleton code. There's a lot you need to fill in before being able to use it.

I typically start testing existing Laravel applications with HTTP Tests. I got tired of running the make:test commands for all my controllers. So here’s a handy one-liner which generates tests for any controller within an application.

find app/Http/Controllers -type f -name '*Controller.php' -exec sh -c 'php artisan make:test $(dirname "${1:4}")/$(basename "$1" .php)Test' sh {} \;

Packages deliver goods

That script helps, but it only scratches the surface. And only works for tests. And only generates stubs.

The bigger time drain is writing model factories. You have to remember the columns, set the proper fake data, wire up the necessary relationships. There goes the morning, if not whole day.

I know I got tired of this and went searching. I found a package by Marcel Pociot. It didn't work out of the box, but I spent time reviving it so I would never write another model factory again.

Like artisan make:factory, this Laravel Test Factory Generator makes the factories for all the models in your application. But it does so much more.

It connects to your local database to determine the columns to create the data definition. Based on the column name and data type, it determines the Faker data to use.

But wait, there's more!

It also looks at the model class to determine the Eloquent relationships and properly creates related model data. So when you're testing, the database has all the necessary records for this model.

So given the following schema:

Schema::create('lessons', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->integer('ordinal');
    $table->unsignedBigInteger('course_id');
    $table->timestamps();
});

This package would generate the following factory:

/* @var $factory \Illuminate\Database\Eloquent\Factory */

use Faker\Generator as Faker;

$factory->define(App\Lesson::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'ordinal' => $faker->randomNumber(),
        'course_id' => function () {
            return factory(App\Course::class)->create()->id;
        },
    ];
});

Whether you have 42 existing models or a few new models this package is a tremendous time saver. Run its artisan command at any point to generate any missing factories for your application with:

php artisan generate:model-factory

Shift into high gear

All these tools are great. But it's still not enough fast enough.

Setup is not where I want to spend my time. I want to write tests! So I started building a Test Generator Shift. Its goal is not only generate the components above, but analyze your codebase to provide a real starting point.

Using route definitions and Controller method signatures, Shift will generate test cases with factories setup, the HTTP request built, and initial assertions. It also adds actionable comments to help you identify additional test paths and setup.

I completed alpha testing earlier this week. From this simple route:

Route::get('promotions/{code}', 'CouponController@show');

The Test Generator Shift created the following HTTP Test:

<?php

namespace Tests\Http\Controllers;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CouponControllerTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function show_returns_an_ok_response()
    {
        $this->markTestIncomplete('This test case was generated by Shift and needs review.');

        $coupon = factory(\App\Coupon::class)->create();

        $this->get('promotions/' . $coupon->code);

        $response->assertOk();

        // TODO: perform additional assertions...
    }
}

I'll do beta testing after Laracon US. I plan to release this Shift early August to give you enough time to start adding tests to your Laravel applications before the release of Laravel 5.9.

This is the next level. And I'm so excited to help provide even more confidence around the process of maintaining your Laravel applications. 👍🏻


Need to test your Laravel applications? Check out the Test Generator Shift to quickly generate tests for an existing codebase and the Confident Laravel video course for a step-by-step guide from no tests to a confidently tested Laravel application.