Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

7th January 2018 4:32 pm

More Tricks for Speeding Up Your Laravel Test Suite

When you first start doing test-driven development with Laravel, it can be quite hard to produce a test suite that runs quickly enough. The first time I used Laravel for a large project, I had a test suite that at one time, took over seven minutes to run, which was pretty awful considering that the ideal time for a test suite to take to run is no more than ten seconds.

Fortunately, with experience you can pick up some techniques which can quite drastically speed up your test suite. Here are some of the ones I’ve learned that can be useful.

Note that some of these are contradictory, and what works for one use case won’t necessarily work for another, so my advice is to try these and see what makes a difference for your use case.

Reduce the cost of hashing

Inside the createApplication() method of tests\CreatesApplication.php, place the following statement:

        Hash::setRounds(4);

This makes hashing passwords quicker and less demanding, and since you don’t care about the security of a password in a test, you’re not losing out in any way by doing so.

This, by itself, can massively reduce the time taken by your test suite - your mileage may vary, but I’ve personally seen it cut to a third of the previous time by using this. In fact, it’s recently been added to Laravel by default.

If you’re creating a lot of fixtures for tests, do so in a transaction

Sometimes, your application requires a lot of data to be added to the database just to be usable, and it’s quite common to use seeders for this purpose. However, it can take some time to insert a lot of data, especially if it has to be re-run for every test. If you do have to insert a lot of data before a test, you can cut down the time substantially by wrapping the seeder calls in a transaction:

<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use DB;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
DB::beginTransaction();
$this->call(GroupTableSeeder::class);
$this->call(UserTableSeeder::class);
$this->call(ProjectTableSeeder::class);
DB::commit();
Model::reguard();
}
}
`

I’ve personally seen this trick cut the insert time by over half, every single time the database is seeded. If you don’t have much data to insert, it may not help, but for large amounts of data it can make a big difference.

If a lot of tests need the same data, migrate and seed it first, then wrap the tests in transactions and roll them back afterwards

If multiple tests need to work with the same dataset, you should consider running the migrations and seeders before the first test, and then wrapping each test inside a transaction. That way the data will only be inserted once, and will be rolled back to that initial good state after each test.

protected static $migrated = false;
public function setUp()
{
parent::setUp();
DB::beginTransaction();
}
public function tearDown()
{
DB::rollback();
parent::tearDown();
}
public static function setUpBeforeClass()
{
if (!self::$migrated) {
Artisan::call('migrate:fresh');
Artisan::call('db:seed');
self::$migrated = true;
}
}

Using something like this instead of one of the existing testing traits may be a better fit under those circumstances. However, if your application uses transactions for some functionality this might cause problems.

Don’t create a full instance of the Laravel application unless you have to

Not every test requires that you instantiate the full Laravel application, and doing so slows your tests down. If you don’t absolutely need the full application instantiated in the test, consider having your test inherit from the below simple test case class instead:

<?php
namespace Tests;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase as BaseTestCase;
class SimpleTestCase extends BaseTestCase
{
use MockeryPHPUnitIntegration;
}

For properly isolated unit tests, using this base class instead can have a noticeable effect on performance.

If you can, use an in-memory SQLite database for testing

This isn’t an option if you’re relying on the features of another database, but if it is, this is usually the fastest way to go. Configure it as follows in phpunit.xml:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

Use the new Refresh Database trait

   use RefreshDatabase;

This testing trait is generally more efficient than migrating down and up, because it empties the database afterwards rather than stepping through the changes of each migration. If you have a non-trivial number of migrations, it will almost certainly be quicker than migrating down, then back up for the next test.

Mock what you can’t control

You should never, ever be making calls to external APIs in your test suite, because you can’t control whether those external API’s work - if a third-party API goes down, you may get a failed test run even if your application is working perfectly, not to mention it will add the time taken to send the request and receive a response to the test time. Instead, mock the calls to the third-party API.

For large applications, consider moving parts into a separate package

If you have a particularly large application, it’s worth considering moving parts of it out into standalone packages and requiring them using Composer’s support for private Git repositories. That way, those packages can have their own test suites, and the main application’s test suite can cover the remaining functionality.

For instance, it’s fairly straightforward to pull out your models and migrations and put them in a separate package, and the tests for them can go with them to that package.

You should also consider whether parts of your application would be useful as standalone packages, and if so pull them out along with their tests. That way, not only are you making your test suite quicker, but you’re also saving yourself work by creating a reusable solution for a problem you might encounter again in the future.

Turn off XDebug

XDebug has a horrendous effect on the performance of the test suite. Turn it off unless you need it to generate test coverage. Better yet, set up continuous integration and have that generate the coverage for you.

Summary

When you first start using Laravel, it can be hard to keep your test suite lean, and the longer a test suite takes to run, the less likely it is to actually get run regularly. To practice TDD properly, your test suite should not take long enough that your mind starts to wander, and ten seconds is a good target to aim for in this regard - you need to be able to run it several times a minute without problem. Obviously things like having a faster computer or an SSD will help, but there’s a lot you can do to make your test suite more efficient, even when running on a quite basic machine.

3rd January 2018 11:49 am

Adding Dynamic Flat Pages to Your Laravel App

Most web apps have at least some need for some additional flat pages, for purposes such as:

  • Terms and conditions
  • Cookie/privacy policy
  • FAQ

You can of course hard-code this content in a view file, but if this content is likely to change often it may be useful to give the site owners the capability to manage this themselves.

Laravel Flatpages is a package I wrote that adds a flatpage model, controller and view to your application. It’s loosely inspired by Django’s flatpages app. Using it, you can quickly and easily build a very simple brochure-style CMS. Each page contains fields for the title, content, slug, and an optional template field that specifies which view to use.

Note that it doesn’t include any kind of admin functionality, so you’ll need to add this yourself or find a package for it. It uses my repositories package to access the database, and this has caching built in, so when you create, update or delete a flatpage, you should either resolve Matthewbdaly\LaravelFlatpages\Contracts\Repositories\Flatpage and use the methods on that to make the changes (in which case the appropriate caches should be flushed automatically), or flush the cache. It also requires a cache backend that supports tags, such as Memcached or Redis.

It does not include routing in the package itself because I couldn’t find a way to guarantee that it would always be the last route, so instead you should put this in your routes/web.php and make sure it’s always the last route:

Route::get('{path}', '\Matthewbdaly\LaravelFlatpages\Http\Controllers\FlatpageController@page');

Otherwise you could wind up with problems. The reason for that is that it has to check the path against the slugs of the flat pages in the database, and if it doesn’t find any it raises a 404.

Or, if you prefer, you can use the middleware at Matthewbdaly\LaravelFlatpages\Http\Middleware\FlatpageMiddleware, which may be more convenient in many case. This should be added as the last global middleware in app\Http\Kernel.php.

2nd January 2018 12:12 pm

A Laravel Package Boilerplate

The second package I’ve been working on recently is Laravel Package Boilerplate. It’s a basic starter boilerplate for building your own Laravel packages.

It’s not meant to be installed as a project dependency. Instead, run the following command to create a new project boilerplate with it:

composer create-project --prefer-dist matthewbdaly/laravel-package-boilerplate <YOUR_NEW_PACKAGE_DIRECTORY>

This will create a new folder that includes a src folder containing a service provider, and a tests folder containing a preconfigured base test case, as well as a simple test case for tests that don’t need the full application instantiated, in order to help keep your test suite as fast as possible.

In addition, it includes configuration files for:

  • PHPUnit
  • PHP CodeSniffer
  • Travis CI

That way you can start your project off the right way with very little effort.

I’ve also added my Artisan Standalone project as a dependency - that way you can access any Artisan commands you need to generate files you need as follows:

$ vendor/bin/artisan

Hopefully this package should make it a lot easier to create new Laravel packages in future.

2nd January 2018 12:01 pm

Using Artisan from Standalone Laravel Packages

Recently I’ve been building and publishing a significant number of Laravel packages, and I thought I’d share details of some of them over the next few days.

Artisan Standalone is a package that, when installed in a standalone Laravel package (eg, not in an actual Laravel install, but in a package that you’re building that is intended for use with Laravel), allows you to use Artisan. It’s intended largely to make it quicker and easier to build functionality as separate packages by giving you access to the same generator commands as you have when working with a Laravel application. It came about largely from a need to scratch my own itch, as when building packages I was having to either run Artisan commands in a Laravel app and move them over, or copy them from existing files, which was obviously a pain in the proverbial.

You can install it with the following command:

$ composer require --dev matthewbdaly/artisan-standalone

Once it’s installed, you can access Artisan as follows:

$ vendor/bin/artisan

Note that it doesn’t explicitly include Laravel as a dependency - you’ll need to add that in the parent package to pull in the libraries it needs (which you should be doing anyway). It’s possible that there are some commands that won’t work in this context, but they’re almost certainly ones you won’t need here, such as the migrate command. As far as I can tell the generator commands, which are the only ones we’re really interested in here, all work OK.

1st January 2018 4:06 pm

Creating Artisan Tasks That Generate Files

While the documentation for creating Artisan tasks is generally pretty good, it doesn’t really touch on creating tasks that generate new files. The only way to figure it out was to go digging through the source code. In this case, I was building an Artisan command to create Fractal transformers as part of a package I’m working on.

There’s a specialised class for generating files at Illuminate\Console\GeneratorCommand, which your command class should extend instead of Illuminate\Console\Command. In addition to the usual properties such as the signature and description, you also need to specify $type to give the type of class being generated. Also, note that the constructor is different, so if you use php artisan make:console to create the boilerplate for this command, you’ll need to delete the constructor.

<?php
namespace Matthewbdaly\MyPackage\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;
class TransformerMakeCommand extends GeneratorCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:transformer {name : The required name of the transformer class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a Fractal transformer';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Fractal transformer';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/transformer.stub';
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name of the command.'],
];
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Transformers';
}
}

Note the getDefaultNamespace() method. If your class will live directly under the app folder this is not necessary. Otherwise, it needs to return the root namespace, with the folder structure you want after it. Here my class will live under app\Transformers, so I’ve set it to reflect that.

Also, note the getStub() method. This tells Artisan that it should use the specified stub file as the basis for our class. Below you’ll find the stub file I used for my transformer:

<?php
namespace DummyNamespace;
use Matthewbdaly\MyPackage\Transformers\BaseTransformer;
use Illuminate\Database\Eloquent\Model;
class DummyClass extends BaseTransformer
{
public function transform(Model $model)
{
return [
'id' => (int) $model->id,
];
}
}

Note that the DummyNamespace and DummyClass fields will be overwritten with the correct values.

Once this Artisan command is registered in the usual way, you can then run it as follows:

$ php artisan make:transformer Example

And it will generate a boilerplate class something like this:

<?php
namespace App\Transformers;
use Matthewbdaly\MyPackage\Transformers\BaseTransformer;
use Illuminate\Database\Eloquent\Model;
class Example extends BaseTransformer
{
public function transform(Model $model)
{
return [
'id' => (int) $model->id,
];
}
}

You can then replace the model with your own one as necessary, and add any further content to this class.

Recent Posts

Mutation Testing With Infection

Switching from Vim to Neovim

Better Strings in PHP

Forcing SSL in Codeigniter

Logging to the ELK Stack With Laravel

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Django, Phonegap and Angular.js.