Setting Up Module-Based Tests for PyroCMS

PyroCMS is quickly becoming one of my favourite packages for building Laravel projects. As much as I adore the basic Laravel features, the time that Pyro has saved me in terms of packaging a nice CMS along with an efficient solution for data structures as well as quick administration form / table builders is significant. Once you get your head around the Streams Platform, and how modules, themes and extensions work, it really is a significant time saver.

However, at first glance it isn’t immediately obvious how you would integrate testing on a per-module basis without coupling your tests to the base PyroCMS installation, which is clunky and inefficient when dealing with many modules with dozens of tests each. In this short blog post, I’ll show you how to set up your modules to be fully testable with a simple  phpunit command.

First, let’s assume we have created a basic module called  ProfilesModule , set up some streams and finally installed it. If you are already lost, I highly recommend the official video series on creating modules, which you can find here.

Now we need to edit our module’s composer.json to require PHPUnit and some of the other testing utilities, as well as provide our classmap for loading our test case.

{
    "name": "rt/profiles-module",
    "type": "streams-addon",
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~4.4|~5.0",
        "symfony/css-selector": "3.1.*",
        "symfony/dom-crawler": "3.1.*"
    },
    "autoload": {
        "psr-4": {
            "Rt\\ProfilesModule\\": "src/"
        }
    },
    "autoload-dev": {
        "classmap": [
            "tests/ProfilesModuleTestCase.php"
        ]
    }
}

By using the classmap, we point our composer.json to our customised ProfilesModuleTestCase.php . Let’s take a look at that file:

<?php

abstract class ProfilesModuleTestCase extends Illuminate\Foundation\Testing\TestCase
{
    
    /**
     * The base URL to use while testing the application.
     *
     * @var string
     */
    protected $baseUrl = 'http://localhost';

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../../../../../bootstrap/app.php';

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        return $app;
    }
}

This syntax should be familiar to anyone who has used Laravel’s default test setup, all we have done is amended our path to reflect the addons location at  addons/<application>/<vendor>/<module>/tests .

Next, we’ll set up a phpunit.xml in the base directory of the module like so:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="../../../../bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./src</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

Here we are leveraging the php environment variables that are provided in the base PyroCMS phpunit.xml as well as modifying a few of our own, setting up the drivers for testing, as well as telling PHPUnit where our autoload file is. We then amend the path of the test suite, in this case  /tests , and whitelist our source directory for PHPUnit.

Now we can run  composer install on our module to install the dependencies required. If you’ve required your addon in your PyroCMS’ composer.json, you can run  composer install on the base installation and it will install these dependencies too. Neat! We should now have a working PHPUnit setup for our module:

PHPUnit for Pyro Module

However, at the moment we aren’t taking advantage of one of the best parts of testing Laravel, Model Factories. By default, Laravel will only load ModelFactory files if they exist in the base  database/factories folder. We’ll use our module’s service provider to instruct Laravel to register our new factory location, addons/<application>/<vendor>/<module>/factories for use in PHPUnit:

<?php namespace Rt\ProfilesModule;

use Anomaly\Streams\Platform\Addon\AddonServiceProvider;
use Illuminate\Database\Eloquent\Factory;

class ProfilesModuleServiceProvider extends AddonServiceProvider
{
    ...

    public function register()
    {
        $this->registerEloquentFactoriesFrom(__DIR__.'/../factories');
    }

    public function map()
    {
    }
    
    /**
     * Register factories.
     *
     * @param  string  $path
     * @return void
     */
    protected function registerEloquentFactoriesFrom($path)
    {
        $this->app->make(Factory::class)->load($path);
    }
}

Now let’s create a basic ModelFactory for our module in  addons/<application>/rt/profiles-module/factories :

 

<?php

$factory->define(Rt\ProfilesModule\Profile\ProfileModel::class, function (Faker\Generator $faker) {
    return [
        'first_name' => $faker->firstName,
        'last_name' => $faker->lastName,
    ];
});

Thanks to our composer.json, we can use the namespace that we have set for the module to access our various classes as normal. Groovy!

Let’s try it out with a simple test that creates a user and generates a profile for it:

<?php

use Rt\ProfilesModule\Profile\ProfileModel;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ProfileTest extends ProfilesModuleTestCase
{    
    use DatabaseTransactions;
    
    /** @test */
    function test_that_a_profile_is_created()
    {
        $profile = factory(ProfileModel::class)->create();
        $this->assertInstanceOf(ProfileModel::class, $profile);
    }
}

And when we run the test:

So there you have it. Setting up individual module tests is just as easy as testing in the main Laravel application, and allows you to store individualised tests within your modules without tying them to any particular installation. If you have any tips or tricks on how to improve the process further, please let me know!

Craig Berry Written by: