Welcome to Abdul Malik Ikhsan's Blog

Functional Test for Zend\Expressive Routed Middleware with Kahlan ^3.0

Posted in testing, Tutorial PHP, Zend Framework by samsonasik on January 13, 2017

You may tried do functional test Zend\Expressive Routed Middleware and end up with “Unable to emit response; headers already sent” error.
This can happen because of during run test, the Test framework itself already run fwrite() or echo to build test report, and make the headers_sent() return true.

To handle that, we can use ob_start(), but since the header is sent in the background, we need to place in both places:

  • test bootstrap
  • before each test

Seriously? Yes! That’s make sure we only get Diactoros response that we use in the buffer to be tested.

Preparation

As usual, we need require kahlan/kahlan:^3.0 in require-dev:

$ composer require --dev kahlan/kahlan:^3.0 --sort-packages

Set Kahlan’s Bootstrap and before each globally

In Kahlan, we can set tests bootstrap and what in all before each test with Kahlan\Filter\Filter in kahlan-config.php, so we can write:

<?php
//kahlan-config.php
use Kahlan\Filter\Filter;

ob_start();
Filter::register('ob_start at each', function($chain) {
    $root = $this->suite();
    $root->beforeEach(function () {
        ob_start();
    });
    return $chain->next();
});
Filter::apply($this, 'run', 'ob_start at each');

Write Spec and Run In Action

Now, if we use Expressive skeleton application, and for example, we need to test App\Action\PingAction routed middleware, we can write spec in spec directory:

.
├── composer.json
├── config
├── data
├── kahlan-config.php
├── public
├── spec
│   └── App
│       └── Action
│           ├── PingActionDispatchSpec.php
├── src
│   └── App
│       └── Action
│           ├── PingAction.php

As the App\Ping\PingAction is return Zend\Diactoros\Response\JsonResponse which contains “ack” data with time() method call:

return new JsonResponse(['ack' => time()]);

The spec can be the following:

<?php
namespace AppSpec\Action;

use Zend\Diactoros\ServerRequest;
use Zend\Expressive\Application;

describe('PingAction Dispatch', function () {

    beforeAll(function() {
        $container = require 'config/container.php';
        $this->app = $container->get(Application::class);
    });

    describe('/api/ping', function () {

        it('contains json "ack" data', function () {

            allow('time')->toBeCalled()->andReturn('1484291901');

            $serverRequest = new ServerRequest([], [], '/api/ping', 'GET');
            $this->app->run($serverRequest);

            $actual = ob_get_clean();
            expect($actual)->toBe('{"ack":"1484291901"}');

        });

    });

});

The ob_start() will automatically called during test bootstrap and before each test.

Now, we can run the test:

$ vendor/bin/kahlan --coverage=4 --src=src/App/Action/PingAction.php 
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| | |

The PHP Test Framework for Freedom, Truth and Justice.

Working Directory: /Users/samsonasik/www/expressive

.                                                                   1 / 1 (100%)



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

Passed 1 of 1 PASS in 0.210 seconds (using 7Mo)

Coverage Summary
----------------
                                        Lines           %

 \                                      1 / 1     100.00%
└── App\                                1 / 1     100.00%
   └── Action\                          1 / 1     100.00%
      └── PingAction                    1 / 1     100.00%
         └── PingAction::__invoke()     1 / 1     100.00%

Total: 100.00% (1/1)

Coverage collected in 0.003 seconds (using an additionnal 0o)

Done 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: