How to create commands to generate custom tests using Laravel

Motivation

Good tech companies know: TDD is important to the health of the project developed in the long run, for several reasons. The era that unit testing was just a luxury or a “waste of time” is gone. But even so, there are those project leaders who refuse to use TDD. There are other scenarios, exist projects with short deadlines or lack of people who know the philosophy of TDD to put into practice.

The company I work for today is young, a little over a year old, and is still shaping a development process, and TDD has not been included in the ideas until the beginning of the year of 2019. Personal and group goals were set within the company in order to achieve the internal improvement and therefore the satisfaction of what was delivered to the customers.

One of the things that were discussed was how to use TDD in our major client system. It was then that a dev, who had already been studying TDD, used in a task that involved a basic CRUD to practice, with some basic tests. As I was studying TDD too, I decided to follow up this task for study purposes. I ended up realizing that this code could be replicated in other cases.

As the other devs did not quite understand TDD, they would certainly have a reasonable time to get the hang of it, since TDD has a somewhat different flow than we had. So I thought of a way to help them. The solution was, based on the tests already done by my coworker, to set up an initial skeleton of what could be a CRUD. The result I explain better below.

The code

To achieve my goal I began to study how Laravel does to generate the commands through make. It uses stubs with the default skeleton and through them creates the php files, performing the replace in key words like class name, which is the parameter that you put when you are doing make, for example.

All file generation classes extend GeneratorCommand, and there are common actions between file generation. So the idea here was to generate a new command with the same hierarchy, overwriting the methods and customizing the way I want.

Bellow each file and let explained what it does inside the code. First step was to create a new command on app/Console/Commands/MakeCustomTest.php

<?php

namespace App\Console\Commands;

use Illuminate\Support\Str;
use Illuminate\Console\GeneratorCommand;

class MakeCustomTest extends GeneratorCommand
{
/**
* The name and signature of the console command.
*
*
@var string
*/
protected $signature = 'make:custom:test {name : The name of the class} {--unit : Create a custom unit test}';

/**
* The name and signature of the console command.
*
*
@var string
*/
protected $name = 'make:custom:test';

/**
* The console command description.
*
*
@var string
*/
protected $description = 'Create a new custom tests class';

/**
* Build the class with the given name.
*
*
@param string $name
*
@return string
*
@throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
protected function buildClass($name)
{
$stub = $this->files->get($this->getStub());

return $this->replaceNamespace($stub, $name)
->replaceModelLowerCasePlural($stub, $name)
->replaceModelLowerCase($stub, $name)
->replaceModel($stub, $name)
->replaceClass($stub, $name);
}

/**
* Replace the model key-word on stub.
*
*
@param string $stub
*
@param string $name
*
@return $this
*/
protected function replaceModel(&$stub, $name)
{
$class_name = $this->getModelName($name);

$stub = str_replace('DummyModel', $class_name, $stub);

return $this;
}

/**
* Replace the model for lower on stub.
*
*
@param string $stub
*
@param string $name
*
@return $this
*/
protected function replaceModelLowerCase(&$stub, $name) {
$class_name = strtolower($this->getModelName($name));

$stub = str_replace('DummyModelLowerCase', $class_name, $stub);

return $this;
}

/**
* Replace the model key-word on stub (plural0.
*
*
@param string $stub
*
@param string $name
*
@return $this
*/
protected function replaceModelLowerCasePlural(&$stub, $name) {
$class_name = str_plural(strtolower($this->getModelName($name)));

$stub = str_replace('DummyModelPluralLowerCasePlural', $class_name, $stub);

return $this;
}

/**
* Get the default namespace for the class.
*
*
@param string $rootNamespace
*
@return string
*/
protected function getDefaultNamespace($rootNamespace)
{
// if you wanted, can use flag --unit for unit
if ($this->option('unit')) {
return $rootNamespace.'\Unit';
} else {
return $rootNamespace.'\Feature';
}
}

/**
* Get the destination class path.
*
*
@param string $name
*
@return string
*/
protected function getPath($name)
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);

return base_path('tests').str_replace('\\', '/', $name).'.php';
}

/**
* Get the stub file for the generator.
*
*
@return string
*/
protected function getStub()
{
if ($this->option('unit')) {
return __DIR__.'/Stubs/unit-test.stub';
}

return __DIR__.'/Stubs/feature-test.stub';
}

/**
* Get the root namespace for the class.
*
*
@return string
*/
protected function rootNamespace()
{
return 'Tests';
}

private function getModelName($name){
return substr(str_replace($this->getNamespace($name).'\\', '', $name), 0, -4);
}
}

I created on folder app/Console/Commands/Stubs for store all stubs and created too the files. Below the example for Feature test:

// app/Console/Commands/Stubs/feature-test.stub
<?php

namespace
DummyNamespace;

use App\DummyModel;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class DummyClass extends TestCase
{
use DatabaseTransactions;

/** @test */
public function an_admin_can_create_a_DummyModelLowerCase()
{
$this->signInUser(); // custom helper

$attributes = factory(DummyModel::class)->raw([
// attributes
]);

$response = $this->post(route('backend.DummyModelPluralLowerCasePlural.store'),
$attributes);

$this->get(route('backend.DummyModelPluralLowerCasePlural.index'))
->assertSee($attributes);

}
}

In this example, I created only the feature test, but if you want, you can create a unit-test.stub file inside the Stubs folder based on it.

Analyzing the Laravel code more closely, and better understanding how certain features work is a great way to study and improve your skills, as well as giving you more ideas on how you can use the framework for your needs in a more personalized way.

That’s it folks, I hope it has been somehow useful my experience along with the code :)