The Workshop: Describe Your Tests with Kahlan - php[architect] Magazine September 2018

Joe • August 27, 2020

learning testing phparch writing

Warning:

This post content may not be current, please double check the official documentation as needed.

This post may also be in an unedited form with grammatical or spelling mistakes, purchase the September 2018 issue from http://phparch.com for the professionally edited version.

The Workshop: Describe Your Tests with Kahlan

by Joe Ferguson

This month we're covering a full featured unit and behavior driven development (BDD) test framework named Kahlan. Kahlan is similar to Rspec (Ruby) and JSpec (Java) using BDD style syntax where you describe the behavior your application should have.

But what about ....?

We already have tons of PHP testing frameworks readily available in the PHP ecosystem such as PHPUnit, phpspec, Codeception, Behat, and too many more to cover. Why should you look into Kahlan when you have so many choices already? Well I'm interested in Kahlan because I'm always interested in how other languages handle the challenges of testing applications and I've been reading The Rspec Book to get an idea how the Ruby community handles specification testing and it's been an enlightening experience.

If you are a new to PHP developer who has maybe used specification testing in another framework or language Kahlan would be an easy way to build robust testing as you're learning PHP. If you are a season, grizzled PHP veteran who swears by and only uses PHPUnit, you'll learn something new with Kahlan.

If you've never written specification tests there is a lot of great information available at betterspecs.org. You'll find helpful tips such as keeping descriptions short to keep the focus narrow and to only create the data you need to keep your tests small and concise.

Another feature Kahlan boasts which drew my attention is Monkey Patching, which is the action of replacing attributes or methods at run time. This allows us to easily stub dependencies without having to use Mockery or Prophecy to mock, stub, or create test doubles or spies.

Tests, tests, so many tests

Traditionally we read about unit testing which is the process of testing single methods in isolation, a unit of our application. An example of a unit test would be to test a method which returns your age based on the year you were born as input. You could write unit tests checking to esnsure if you were born in 1990 your age should be 28.

Stepping up from unit tests are feature testing (also known as Functional Testing) is the process of testing multiple methods are working together to deliver the desired result or feature. This could be used to test our age calculation method also works with our "Happy Birthday" method which displays some extra text if today is your birthday.

We also have Acceptance Tests which a test execute as a user in the application accomplishing a task. This would test a user visiting your application via a browser and taking a series of steps such as logging in and clicking on their profile and if today is their birthday, display a message.

Specification tests are closest to Feature Tests because we're testing more than a single method, but less than several steps. We're writing tests that ensure our application conforms to an expected specification or rule set.

What is a Specification

If you have never heard the term specification in the programming world it's a fancy way of stating a requirement or feature. Your application should conform to a set of features which allow a user to accomplish tasks with your software. These features could easily be described as specifications, or specs for short.

Specifications are great for BDD style testing because you can easily write out tests in plain English which makes your tests easily readable by non-programmers. If your tests are easily readable by non-programmers you can start to have your quality assurance or product managers start writing tests for you right? (I hope to do this on my team one day)

Installation

Kahlan requires Composer, at least PHP 5.5 (I use PHP 7.2 in all of my testing of Kahlan) and Xdebug or phpdbgfor creating coverage reports.

You should be using Composer. If you're not, stop reading and go to getcomposer.org and start using it!

We'll add Kahlan to our project by running composer install:

composer require --dev kahlan/kahlan
Using version ^4.1 for kahlan/kahlan
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 2 removals
  - Removing yab/laravel-scout-mysql-driver (v2.0.1)
  - Removing laravel/scout (v3.0.12)
  - Installing kahlan/kahlan (4.1.6): Downloading (100%)
Writing lock file
Generating optimized autoload files

For our example of installing and learning how to use Kahlan we'll use an existing Laravel application I use to teach Laravel. You can clone the QuickStart 5.5 repository and checkout the branch kahlan. If you want to code along side as you read, checkout the branch exercise-9 then check out a clean branch from there.

Once we have Kahlan installed we can run the command line binary via php vendor/bin/kahlan and see the following output:

vagrant@qs:~/qs$ ./vendor/bin/kahlan
ERROR: unexisting `spec` directory, use --spec option to set a valid one (ex: --spec=tests).

This error message is not exactly what I was expecting, but fair enough, we have not yet created our spec folder to hold our tests. Since I already have several tests in this project in the tests folder I'm going to create a spec folder in the root of our project. Once we have our folder created we see much more friendly output:

vagrant@qs:~/qs$ ./vendor/bin/kahlan
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| |_|

The PHP Test Framework for Freedom, Truth and Justice.

src directory  :
spec directory : /home/vagrant/qs/spec

1 / 1 (100%)

Expectations   : 0 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 0 of 0 PASS in 0.033 seconds (using 4MB)

Always good to know our 0 specifications have passed.

Writing our first specification

To get started we'll create a file called spec/first.spec.php. All of our spec tests will use .spec.php extension so Kahlan knows to execute them. Looking at the documentation we'll take the first example and examine the parts:

describe("ClassName", function() {
    describe("methodName", function() {
        it("passes if true === true", function() {
            expect(true)->toBe(true);
        });
    });
});

The parts of our spec are describe where we start by describing the class name our spec is for, then we start describing the method. The it section is the code we are testing. We expect the boolean true toBe equal to boolean true. Let's run our spec ./vendor/bin/kahlan and see what happens:

src directory  :
spec directory : /home/vagrant/qs/spec
.                                                                   1 / 1 (100%)

Expectations   : 1 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 1 of 1 PASS in 0.046 seconds (using 4MB)

We can see Kahlan ran our spec test and it evaluated true to be true fantastic we now know the truth!

Writing a Controller Specification

Plucking a test from the documentation and running it is all fun and games until we try to write a spec for a real class or method in our application.

In our application we have a HomeController located at app/Http/Controllers/HomeController.php which serves as the home page handler. We have a single method named index() which is used to return our default homepage view to the user. You can see our HomeController source:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function index()
    {
        return view('home');
    }
}

No we're going to create a HomeController.spec.php in our spec folder and add the following code:

<?php
use App\Http\Controllers\HomeController;

describe('HomeController', function() {
    describe('index', function() {
        it('returns the welcome view', function() {
            $instance = new HomeController();
            allow($instance)
                ->toReceive('index')
                ->andReturn('App Name - Home');
            expect($instance->index())
                ->toContain('App Name - Home');
        });
    });
});

We describe our HomeController class and our index method. Since our method returns a static view we want to ensure some text we expect will be returned. We can create a new instance of the controller and then allow() the instance to receive a method call to index then andReturn a string, then we expect the method to contain the string we specified in the andReturn. This is an example of method stubbing. We didn't have to use Mockery or another library to create a stub, we just had to create a new instance of our HomeController and describe what we wanted to allow and what we expected to happen.

Looking at our app/Http/Controllers/TaskController.php we can see we have the following index method similar to our HomeController however we are passing data to the view:

public function index()
{
    $tasks = Task::with('user')->get();

    return view('tasks.index')
        ->with('tasks', $tasks);
}

We can create a TaskController.spec.php with the following to create a specification for the TaskController's index method:

<?php
use App\Http\Controllers\TaskController;
use App\Task;
use Illuminate\Database\Eloquent\Collection;

describe('TaskController', function() {
    describe('index', function() {
        it('contains tasks', function() {
            $task = new Task([
                'name' => 'new Task',
                'user_id' => 1,
            ]);

            $collection = new Collection($task);
            $instance = new TaskController();

            allow($instance)
                ->toReceive('index')
                ->andReturn($collection);

            expect($instance->index())
                ->toContainKeys('name', 'user_id');
        });
    });
});

For our TaskController we want to first verify our index() method contains the data we are passing to it. Our actual index() method queries the database for tasks we want to create a new task, add it to a Laravel collection object since it is what our application will pass to the view helper which will render our specified view template. Once we have a collection with a task in it we can allow() the instance toReceive() the method call andReturn our collection object. Next we expect our $instance->index() will contain keys matching name, and user_id to show the task we created and added to the collection does in fact contain data we are expecting. Running kahlan shows us everything is still good so far:

src directory  :
spec directory : /home/vagrant/qs/spec

...                                                                 3 / 3 (100%)

Expectations   : 3 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 3 of 3 PASS in 0.121 seconds (using 6MB)

Stubbing & Monkey Patching

Monkey patching isn't just fun to say, it's a neat way to make existing classes function in a different way at run time, in our case run time is when we are executing our tests. We can also stub methods and provide return values and allow methods to be called:

it("Stub PDO", function() {
    // Monkey Patch PDO and Return an array of data

    allow('PDO')
        ->toReceive('prepare->fetchAll')
        ->andRun(function() {
        return [['name' => 'Joe']];
    });

    // Swap out PDO for our own class
    allow('PDO')->toBe('My\Alternative\PDO');
});

Monkey patching is how you can easily swap out large classes like PDO with anonymous classes you create on the fly. No need to keep a bunch of fake classes laying around just for your tests. I highly recommend making use of PHP's anonymous classes, they make creating test doubles a breeze.

Beware to use PHP's Anonymous Classes you must be on at least PHP 7 which should be no big deal since everyone has already upgraded and eagerly awaiting PHP 7.3 right?

Popular Framework Integration

The Kahlan Documentation features a section on integrating with various frameworks. The jarektkaczyk/laravel-kahlan package allows you to use Laravel's own test helpers in your specifications. If you're already using these helpers you can easily convert your tests to specifications with this plugin. There is also a symfony bundle available for writing Symfony specifications.

Recap

Take Kahlan out for a test drive on your own application and let me know what you think. Kahlan has definitely given me another approach to testing applications. You'll learn a lot about specifications if you go through your existing tests and turn them into specifications. If you don't have existing tests there is no time like the present to get started!

Happy Testing!

Warning:

This post content may not be current, please double check the official documentation as needed.

This post may also be in an unedited form with grammatical or spelling mistakes, purchase the September 2018 issue from http://phparch.com for the professionally edited version.