Testing Zend Framework applications with Behat and PHPUnit

In my previous post "Debugging Zend Framework routes with ZF Debug Utils" I presented a tool that I wrote to ease my way around ZF routing. Today I'm bringing another Zend Framework related project that I built to help me test ZF applications.

Unit testing a Zend-mvc application

If you drill down to the tutorials section in the ZF documentation (ZF3 is the latest version at the time of writing) you will find this comprehensive article about "Unit Testing a zend-mvc application". If you are still using ZF2 you can replace "zend-mvc" with "Zend Framework 2" and it will mean the same thing. You may even find the ZF2 version of the article I just mentioned.

In that tutorial you can learn how to setup your test suite and write a controller test. If you haven't read the tutorial yet please give it a go. The crucial component there is zend-test, a Zend component that provides PHPUnit integration for zend-mvc, including application scaffolding and custom assertions.

After setting the path to your application config in a TestCase class you will be able to test your controllers using assertions such as demonstrated below:

class AlbumControllerTest extends AbstractHttpControllerTestCase
{
    // setUp() { ... }
    public function testIndexActionCanBeAccessed()
    {
        $this->dispatch('/album');
        $this->assertResponseStatusCode(200);
        $this->assertModuleName('Album');
        $this->assertControllerName(AlbumController::class);
        $this->assertControllerClass('AlbumController');
        $this->assertMatchedRouteName('album');
    }
}

Customising components for testing

By using AbstractHttpControllerTestCase we are indeed booting a ZF application but we are not forced to use the full blown app that you push to production with database(s) and 3rd-party services.

As opposed to black-box and end-to-end testing we do have the possibility to modify our application configuration and disable or mock certain components, usually external, such as a database. In order to do so we can provide test configuration and ask the Service Manager to be less strict and allow us to override its original configuration and services. This scenario is perfectly illustrated in the tutorial's "Configuring the service manager for the tests" section.

Bringing PHPUnit and Behat together

We have seen so far that we can write assertions using zend-test and PHPUnit and mock certain components of the application. Now, in addition to that, wouldn't it be nice if we could write and document our tests using Gherkin language and Behat?

Behat is an open source Behavior-Driven Development framework for PHP. It is a tool to support you in delivering software that matters through continuous communication, deliberate discovery and test-automation.

We can now write the following feature and use it to power our tests:

Feature: Inventory system to display which albums we own
  In order to keep track of the albums I own
  As an avid music collector
  I need to be able to add, edit and list music albums
  
  Scenario: Adding a new album to my collection
    Given I own the album "Wolfheart" by "Moonspell"
    When I add the album "Wolfheart" by "Moonspell" to my collection
    Then I should have 1 album in my music collection

And in order to do that we just need the last piece of the puzzle, the ZF Test Case Behat Extension.

ZF Test Case Behat Extension

ZF Test Case Behat Extension is a Behat extension that exposes Zend\Test classes originally built for PHPUnit and makes them available in your Behat Context classes.

Installation

This extension requires Behat 3.0+ and the recommended installation method is through Composer:

$ composer require --dev noiselabs/zf-test-case-behat-extension

You can then activate the extension in your behat.yml configuration file:

default:
    # ...
    extensions:
        Noiselabs\Behat\ZfTestCaseExtension\ServiceContainer\ZfTestCaseExtension:
            configuration: </path/to/application.config.php>

Usage

Implement ZfTestCaseAwareContext or extend ZfTestCaseContext in your Context classes. The ZfTestCaseAwareInitializer class included in the Behat extension will then call the setTestCase method and inject an instance of HttpControllerTestCase.

<?php

namespace AlbumTest\Functional;

use Album\Controller\AlbumController;
use Noiselabs\Behat\ZfTestCaseExtension\Context\ZfTestCaseAwareContext;
use Noiselabs\Behat\ZfTestCaseExtension\TestCase\HttpControllerTestCase;

class AlbumContext implements ZfTestCaseAwareContext
{
    /**
     * @var HttpControllerTestCase
     */
    private $testCase;

    public function setTestCase(HttpControllerTestCase $testCase)
    {
        $this->testCase = $testCase;
    }

    /**
     * @When /^the album endpoint is called$/
     */
    public function testIndexActionCanBeAccessed()
    {
        // See https://docs.zendframework.com/tutorials/unit-testing/
        $this->testCase->dispatch('/album');
        $this->testCase->assertResponseStatusCode(200);
        $this->testCase->assertModuleName('Album');
        $this->testCase->assertControllerName(AlbumController::class);
        $this->testCase->assertControllerClass('AlbumController');
        $this->testCase->assertMatchedRouteName('album');
    }
}

You can now follow the examples shown in "Your first controller test" and use all the methods provided by Zend's AbstractHttpControllerTestCase in your Behat context classes.

Happy testing!


Credits: Header photo by Ed Robertson on Unsplash