Functional Test for Zend\Expressive Routed Middleware with Kahlan ^3.0
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 comment