Unit and Functional testing Zend Framework 3 Controller with Kahlan 3.0
This post will cover unit and functional testing ZF3 Controller with Kahlan 3.0. For example, you have a ZF3 Skeleton application with an IndexController
like the following:
namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class IndexController extends AbstractActionController { public function indexAction() { return new ViewModel(); } }
As usual, we need to require kahlan/kahlan:^3.0
via composer command:
composer require --dev kahlan/kahlan:^3.0 --sort-packages
You can then write the spec. Let’s write our spec inside module/Application/spec
like the following structure:
module/Application/ ├── config ├── spec │ ├── Controller │ │ ├── IndexControllerDispatchSpec.php │ │ └── IndexControllerSpec.php ├── src │ ├── Controller │ │ ├── IndexController.php
if we are only have the 1 module, named Application
module, we can define the spec
and src
path via kahlan-config.php like the following:
// ./kahlan.config.php $commandLine = $this->commandLine(); $commandLine->option('spec', 'default', 'module/Application/spec'); $commandLine->option('src', 'default', 'module/Application/src');
Or for multi-modules, we can run parallel command that specify --spec
and --src
in command like the following:
vendor/bin/kahlan --spec=module/Application/spec --src=module/Application/src
in each iteration. If you’re using ant
, you can write a build.xml
for tasks definition:
<?xml version="1.0" encoding="UTF-8"?> <project name="My Website" default="build"> <!-- executable files directory definition --> <property name="toolsdir" value="${basedir}/vendor/bin/"/> <!-- module directory definition --> <property name="moduledir" value="${basedir}/module/"/> <target name="build" depends="kahlan" description=""/> <target name="kahlan" description="Run kahlan"> <parallel> <!-- Application --> <exec executable="${toolsdir}kahlan" failonerror="true" taskname="kahlan"> <arg line="-spec=${moduledir}Application/spec/ --src=${moduledir}Application/src"/> </exec> <!-- Application --> <!-- other modules run test definition go here --> </parallel> </target> </project>
Unit testing
Let’s write the unit testing inside spec/Controller/IndexControllerSpec.php
:
namespace ApplicationSpec\Controller; use Application\Controller\IndexController; use Zend\View\Model\ViewModel; describe('IndexController', function () { given('controller', function () { return new IndexController(); }); describe('->indexAction()', function() { it('instance of ViewModel', function() { $actual = $this->controller->indexAction(); expect($actual)->toBeAnInstanceOf(ViewModel::class); }); }); });
That’s enough for IndexController::indexAction()
unit test, nothing complex logic we need to accomodate as it only return the ViewModel
instance, so we just need to check if return values is instance of ViewModel
.
Functional Testing
Now, we need to make sure if the dispatch response of IndexController::indexAction()
by open ‘/’ url that shown by user is the expected result, that show a welcome page, let’s do with spec/Controller/IndexControllerDispatchSpec.php
:
namespace ApplicationSpec\Controller; use Zend\Console\Console; use Zend\Mvc\Application; describe('IndexController Dispatch', function () { // setup the Application beforeAll(function () { Console::overrideIsConsole(false); $appConfig = include __DIR__ . '/../../../../config/application.config.php'; $this->application = Application::init($appConfig); $events = $this->application->getEventManager(); $this->application->getServiceManager() ->get('SendResponseListener') ->detach($events); }); // dispatch '/' page tests describe('/', function() { it('contains welcome page', function() { $request = $this->application->getRequest(); $request->setMethod('GET'); $request->setUri('/'); // run app with '/' url $app = $this->application->run(); // expect actual response is contain // a welcome page expect( $app->getResponse()->toString() )->toContain('<h1>Welcome to <span class="zf-green">Zend Framework</span></h1>'); }); }); });
That’s it 😉
Testing CodeIgniter application with Kahlan 3.0
Really? Yes, it is testable with kahlan
– The PHP Test Framework for Freedom, Truth, and Justice -. Let’s give a try, I am using CodeIgniter 3.1.0 for this example. You can download CodeIgniter from codeigniter.com . For example, we are going to test its Welcome
controller.
Setup:
a. require kahlan/kahlan
composer require --dev kahlan/kahlan:^3.0
b. setup minimal autoloading in kahlan-config.php
in root CodeIgniter project with Kahlan\Filter\Filter::register()
to register its autoloader:
<?php // ./kahlan.config.php use Kahlan\Filter\Filter; define('CI_VERSION', '3.1.0'); define('ENVIRONMENT', 'development'); define('APPPATH', 'application/'); define('VIEWPATH', 'application/views/'); define('BASEPATH', 'system/'); require_once BASEPATH . 'core/Common.php'; function &get_instance() { return CI_Controller::get_instance(); } Filter::register('ci.autoloader', function($chain) { $this->autoloader()->addClassMap([ // core 'CI_Controller' => BASEPATH . 'core/Controller.php', // controllers 'Welcome' => APPPATH . 'controllers/Welcome.php', ]); return $chain->next(); }); Filter::apply($this, 'namespaces', 'ci.autoloader');
c. Define the spec, we can create spec/controllers
directory for placing controller spec:
application/ spec/ └── controllers └── WelcomeSpec.php system/ kahlan-config.php
d. Write the spec:
<?php describe('Welcome', function () { describe('->index()', function () { it('contains welcome message', function() { $controller = new Welcome(); ob_start(); $controller->index(); $actual = ob_get_clean(); expect($actual)->toContain('Welcome to CodeIgniter!'); }); }); });
e. run the kahlan command
vendor/bin/kahlan --coverage=4 --src=application/
and you will get the following output:
What If we load model into controller ? How to test ?
We can also, For example, you have a model named Welcome_model
which check what passed name that will be used in controller:
<?php // application/models/Welcome_model.php class Welcome_model extends CI_Model { public function __construct() { parent::__construct(); } public function greeting($name) { if (trim($name) === '') { return 'Hello Guest'; } return 'Hello ' . $name; } }
As we need to check uri segment, we need to register new route in application/config/routes.php
:
$route['welcome/:name'] = 'welcome/index';
And now, we load in controller:
<?php // application/controllers/Welcome.php class Welcome extends CI_Controller { public function __construct() { parent::__construct(); $this->load->model('Welcome_model', 'welcome'); } public function index() { $greeting = $this->welcome->greeting($this->uri->segment(3)); $this->load->view('welcome_message', ['greeting' => $greeting]); } }
On view ( application/views/welcome_message.php
), we modify the greeting:
<?php // application/views/welcome_message.php ?> <h1><?php echo $greeting; ?>, Welcome to CodeIgniter!</h1>
At this case, we need a CI_URI::segment()
and Welcome_model::greeting()
to be stubbed in the spec, so, we need to modify our kahlan-config.php
to register CI_URI
, CI_Model
and its Welcome_model
classes:
<?php // ./kahlan-config.php use Kahlan\Filter\Filter; define('CI_VERSION', '3.1.0'); define('ENVIRONMENT', 'development'); define('APPPATH', 'application/'); define('VIEWPATH', 'application/views/'); define('BASEPATH', 'system/'); require_once BASEPATH . 'core/Common.php'; function &get_instance() { return CI_Controller::get_instance(); } Filter::register('ci.autoloader', function($chain) { $this->autoloader()->addClassMap([ // core 'CI_Controller' => BASEPATH . 'core/Controller.php', 'CI_URI' => BASEPATH . 'core/URI.php', 'CI_Model' => BASEPATH . 'core/Model.php', // controllers 'Welcome' => APPPATH . 'controllers/Welcome.php', // models 'Welcome_model' => APPPATH . 'models/Welcome_model.php', ]); return $chain->next(); }); Filter::apply($this, 'namespaces', 'ci.autoloader');
Then, here is the spec we will need to have:
<?php use Kahlan\Plugin\Double; describe('Welcome', function () { describe('->index()', function () { it('contains welcome message to specific passed name parameter', function() { define('UTF8_ENABLED', TRUE); // used by CI_Uri allow('is_cli')->toBeCalled()->andReturn(false); // to disable _parse_argv call // stubs CI_Uri::segment() $uri = Double::instance(['extends' => 'CI_URI']); allow($uri)->toReceive('segment')->with(3)->andReturn('samsonasik'); // stubs Welcome_model::greeting() $welcome_model = Double::instance(['extends' => 'Welcome_model']); allow($welcome_model)->toReceive('greeting') ->with('samsonasik') ->andReturn('Hello samsonasik'); $controller = new Welcome(); $controller->uri = $uri; $controller->welcome = $welcome_model; ob_start(); $controller->index(); $actual = ob_get_clean(); expect($actual)->toContain('Hello samsonasik, Welcome to CodeIgniter!'); }); }); });
As we are stubbing Welcome_model::greeting()
, here is the expected output that will be shown on run test:
.
If we want to make Welcome_model::greeting()
coverable, we can create a new spec for testing real Welcome_model::greeting()
call.
You wanna grab full sample? I created a repository for it so you can try: https://github.com/samsonasik/ci_310_with_kahlan 😉
Done 😉
2 comments