CakePHP and PHPUnit

Earlier I wrote about some PHPUnit tips around Cake2. At that time the old SimpleTest testing framework had just been replaced by PHPUnit. So everything around this new framework was fairly new – especially to be.

I manually installed/downloaded the test suite files since I didnt want to install any PEAR stuff – on windows this is pretty annoying anyway.
So is there a quick way to always be up to date with PHPUnit? Yes, there is. Since it is now pretty stable I want to introduce the PHPUnit plugin, which I developed together with Hyra.

Installing PHPUnit – the easy way

It never was easier. All you need for it is the PHPUnit plugin. Just open your console in your app directory and type

cake Phpunit.Phpunit install

Select your destination (APP or ROOT vendors) and done.
This will download and extract all the necessary files, and put them in your specified Vendor folder.

Note that will have to use your path to the cake console here instead of just "cake" – e.g. /Console/cake or ../lib/Cake/Console/cake.

Make sure you got CakePlugin::loadAll() – or specifically CakePlugin::load(‘Phpunit’) in your bootstrap! Otherwise the plugin will not be available.
You can now use PHPUnit through the CLI or your favourite browser right away.

See the documentation of the plugin for details. Also note the convenience tasks (‘packages’, ‘info’) to check for updates and update the plugin shell if necessary.
It also displays the so far unused pear packages.

Autoload

If you have it installed in your ROOT vendors and get some include warnings while baking put this at the top of the VENDORS/PHPUnit/Autoload.php file:

set_include_path(get_include_path().PATH_SEPARATOR.dirname(dirname(__FILE__)));

This way the vendors folder itself is also an include path and those warnings will go away.

Further setup and usage

For this I can just refer you to the official documentation.

Appendix

In my case just now, info printed the following:

# PHPUnit 3.6.11
        OK (v3.6.11)
# File Iterator 1.3.1
        OK (v1.3.1)
# Text Template 1.1.1
        OK (v1.1.1)
# PHP CodeCoverage 1.1.3
        OK (v1.1.3)
# PHP Timer 1.0.2
        OK (v1.0.2)
# PHPUnit MockObject 1.1.1
        OK (v1.1.1)
# PHP TokenStream 1.1.3
        OK (v1.1.3)
# DbUnit 1.1.2
        OK (v1.1.2)
# PHPUnit Story 1.0.0
        OK (v1.0.0)
# PHPUnit Selenium 1.2.7
        OK (v1.2.7)
# PHPUnit TicketListener GitHub 1.0.0
        OK (v1.0.0)
---------------------------------------------------------------
Unused pear packages:
# FinderFacade (v1.0.1)
# Object_Freezer (v1.0.0)
# PHPUnit_SkeletonGenerator (v1.1.0)
# PHPUnit_TestListener_DBUS (v1.0.0)
# PHPUnit_TestListener_XHProf (v1.0.0)
# PHPUnit_TicketListener_Fogbugz (v1.0.0)
# PHPUnit_TicketListener_GoogleCode (v1.0.0)
# PHPUnit_TicketListener_Trac (v1.0.0)
# PHP_CodeBrowser (v1.0.2)
# PHP_Invoker (v1.1.0)
# bytekit (v1.1.2)
# hphpa (v1.2.2)
# phpcov (v1.0.0)
# phpcpd (v1.3.5)
# phpdcd (v0.9.3)
# phploc (v1.6.4)
# ppw (v1.0.4)
# test_helpers (v1.1.0)

Available asserts

In Cake2.x and PHPUnit 3.x you have all the basic asserts available.
These include the most important ones:

  • assertTrue and assertFalse as well as assertNull
  • assertEmpty and assertNotEmpty (for arrays)
  • assertContains and assertNotContains (for strings)
  • assertEquals and assertSame (the latter also asserts the same type) and their not counterparts
  • assertLessThanOrEqual for numeric comparison
  • assertRegExp and assertNotRegExp for regexp pattern checks

On top of that, CakePHP has a few built in additions:

  • assertWithinMargin to check that a float values is within a specific value range
  • assertTextContains and assertTextNotContains to compare texts that can contain different EOL chars
  • assertTextStartsWith and assertTextEndsWith and their not counterparts to check only for the beginning or end of a string, ignoring the EOL char type
  • assertTags as CakePHP specific helper method to assert tags

A first test case

Let’s use a concrete real-life scenario. We can use the NumberLib class of my Tools plugin for this.
If you want to assert a method "average"/test in this Lib class works as expected, we first need to set up a test case file for it in /Test/Case, suffixing it with "Test":

App::uses('NumberLib', 'Tools.Utility');

class NumberLibTest extends CakeTestCase {
}

The structure of the folders in Test should be identical to the app/plugin ones. So we create it in APP/Plugin/Tools/Test/Case/Lib/Utility/NumberLibTest.php.
Also, don’t forget to App::uses() the class you want to test (and possible dependencies that are not directly loaded by that class itself).

Then use the setUp() callback to create an object for later use:

public $Number;

public function setUp() {
    parent::setUp();

    $this->Number = new NumberLib();

    // Optional custom stuff to reset/init etc.
}

We are all set, we can start with a basic test.
As a convention, test methods always start with "test". Without that they will not be recognized.

For some classes it is useful to assert the object is the correct one (and our loading process doesn’t load a wrong one).
Here we just do it for the sake of completeness:

public function testObject() {
    $this->assertInstanceOf('NumberLib, $this->Number);
}

OK, but let’s start a real test now – the test for our "average" method:

public function testAverage() {
    // It is always a good idea to test "margins", in this case an empty array coming in
    $array = array();
    $result = $this->Number->average($array);
    $expected = 0.0;
    $this->assertSame($expected, $result);

    // Then test some common values and leave the options array alone for now
    $array = array(3, 8, 4);
    $result =  $this->Number->average($array);
    $expected = 5.0;
    $this->assertSame($expected, $result);

    // Then at some point also test with the other arguments set to different values
    $array = array(0.0, 3.7);
    $result =  $this->Number->average($array, 1); // Precision of 1 insteaf of default 0
    $expected = 1.9;
    $this->assertSame($expected, $result);
}

I think that gives you a pretty good idea how it works 🙂 See more tests (in this plugin or the CakePHP core code) for more examples.

Pitfalls and tips

Sometimes you need to debug your error throwing test case. If debug() doesn’t work for you then because the fail swallows the output. Use this then:

debug($someVar); ob_flush();

The ob_flush() forces the output of the debug content even if the assert afterwards fails.

3 Comments

  1. I still cannot get this to work properly , i have followed the entire process with my vendor’s folder populated yet i ge ta fata error saying cake cannot find this path
    PHPUnit/Framework/MockObject/Autoload.php

  2. I have added this line to the top of my Autoload.php file, i tried to install in both root cakephp installation folder as well as my solution’s vendor folder.

    set_include_path(get_include_path().PATH_SEPARATOR.dirname(dirname(FILE)));

    The fatal error might be due to this file not being found
    Warning (2): require_once(PHPUnit/Framework/MockObject/Autoload.php)

    I am using XAMMP and the include path points to my xamppfiles/ directory with php unit installed there

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.