How to type less $this in PHPUnit

Lately I’ve been writing a few tests (using PHPUnit), and when you do there’s one thing you end up doing a lot, which is typing $this-> whenever you need to do pretty much anything. It’s not that bad (and PHP can be quite verbose itself) but it can become a bit of a bore I guess, especially after a while.

Apparently, it’s been one of the things PHPUnit’s author Sebastian Bergmann has been asked about a lot, so much so that eventually he added a way to type less of those, by introducing a new set of functions. This wasn’t well received by the community, for understandable reasons, such as those functions clutter the main namespace and illustrate “bad practice” (in the oriented object world) while PHPUnit is all about the exact opposite.

Those were valid points, and for a while that feature was even temporarily removed. So.. now what? Back to typing all those $this-> over & over again? Maybe not.

Looking at the different options at our disposal

The functions were temporarily removed, but are now a part of PHPUnit, and can still be used. They’re not available by default (you’ll need to include PHPUnit/Framework/Assert/Functions.php before you can use them) but they exists, although you might not want to use them, for the same reasons people didn’t like their additions and that had them removed, if only for a while.

There are a few other options one could use to reduce this, such as extending PHPUnit_Framework_TestCase to create shorthands, e.g. a method equals or even just ae instead of having to use assertEquals. Although you’d still have to use $this with that solution.

And while the original methods actually are static (you could actually use self::assertEquals just as well), your new methods wouldn’t have to be, therefore you could even add a fluent interface in your extended test case.

This is (almost) what I did; Almost, because I didn’t actually extend PHPUnit_Framework_TestCase but PHPUnit_Framework_Assert, which holds all of the assertion methods. It still allows to do exactly the same (even provide a fluent interface) but you can reduce the typing even more, since you don’t even need to use $this at all (which wouldn’t have been possible if extending the test case).

Examples of a test, the $this->usual() way

First let’s have a look at what a test would look like, the usual way using $this-> as we see everywhere. (Note that you could actually use self:: just as well, since, again, all the assertion methods are static.)

class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        $this->assertEquals(0, count($stack));
 
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));

        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}

As of PHPUnit 3.5, you can reduce things a little by using the functions and have it look like the following:

require_once 'PHPUnit/Framework/Assert/Functions.php';
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        assertEquals(0, count($stack));

        array_push($stack, 'foo');
        assertEquals('foo', $stack[count($stack)-1]);
        assertEquals(1, count($stack));

        assertEquals('foo', array_pop($stack));
        assertEquals(0, count($stack));
    }
}

Externalizing assertions methods, adding a few shorthands

As I said, I decided to extend PHPUnit_Framework_Assert in a new class where I could have shorthands as well as provide a fluent interface, all the while making it available first through a short static call. The result could be something like the following :

require_once 'Assert.php';
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        assert::equals(0, count($stack));

        array_push($stack, 'foo');
        assert::equals('foo', $stack[count($stack)-1])
              ->equals(1, count($stack))

              ->equals('foo', array_pop($stack))
              ->equals(0, count($stack))
              ;
    }
}

This looks quite similar, but we don’t actually use any functions, and save a few keystrokes through the use of the fluent interface. It could even make a bit more smaller by using an alias for the class name, and aliases for the actual methods called as well. Pushed to the extreme, you could reduce assert::equals() down to a simple a::e(), but you risk making your tests less readable, which isn’t really a good thing.

So as a middle ground, the example could eventually look like the following:

require_once 'Assert.php';
use Assert as is;

class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        is::eq(0, count($stack));

        array_push($stack, 'foo');
        is::eq('foo', $stack[count($stack)-1])
          ->eq(1, count($stack))

          ->eq('foo', array_pop($stack))
          ->eq(0, count($stack))
          ;
    }
}

Since we already have a requirement of PHP 5.3.0 we might as well use its namespace feature, and I like to call this new Assert class “is”, that way you can get things like:

is::eq(42, $answer)
  ->true($object->isFoo())
  ->false($object->isBar())
  ->notSame($object, clone $object)
  ;

I believe this manages to be shorter that than usual $this->assertEquals() way, not make use of any functions, in the global namespace or not, and keeps things still be pretty readable.

The extended Assert class

Note that the following requires PHP 5.3.0 or later.

As illustrated above, this class will allow you to use all of PHPUnit’s assertion methods, without the prefix “assert” (or the $this). Note that instead of writing a method for each of the existing PHPUnit method, I just used PHP’s magic methods which allows to add support for every method at once. And of course, because it extends PHPUnit_Framework_Assert you can obviously use the original methods. In addition to dropping the “assert” prefix, you can define aliases (in the $_alias private property) which can be used instead.

Note that because aliases must define the name of the PHPUnit methods to be called, you can also add aliases for other available methods, such as isFalse, stringStartsWith, logicalOr, etc)

class Assert extends PHPUnit_Framework_Assert
{
    /**
     * Alias for PHPUnit's assertions methods
     *
     * The key is the alias name, which can be used as a method, and the value is the full name of the (PHPUnit) method
     *
     * @var array
     */
    static private $_alias = array(
        'eq'        => 'assertEquals',
        'sm'        => 'assertSame',
    );
    
    /**
     * Calls the method specified by its name (minus "assert") or alias (static calls)
     *
     * @param string $name    name of the method to call (minus prefix "assert") or alias
     * @param array  $args    parameters for the method
     *
     * @throws Exception      method doesn't exists
     * @return void
     */
    static public function __callStatic($name, array $args)
    {
        self::_call($name, $args);
        $class = __CLASS__;
        return new $class;
    }
    
    /**
     * Calls the method specified by its name (minus "assert") or alias (instantiated calls)
     *
     * @param string $name    name of the method to call (minus prefix "assert") or alias
     * @param array  $args    parameters for the method
     *
     * @throws Exception      method doesn't exists
     * @return void
     */
    public function __call($name, array $args)
    {
        self::_call($name, $args);
        return $this;
    }
    
    /**
     * Calls the method specified by its name (minus "assert") or alias
     *
     * @param string $name    name of the method to call (minus prefix "assert") or alias
     * @param array  $args    parameters for the method
     *
     * @throws Exception      method doesn't exists
     * @return void
     */
    static private function _call($name, array $args)
    {
        if (isset(self::$_alias[$name]))
        {
            $name = self::$_alias[$name];
        }
        else
        {
            $name = 'assert' . ucfirst($name);
        }
        
        if (!method_exists(__CLASS__, $name))
        {
            throw new Exception('Unknown assert method:' . $name);
        }
        
        $class = __CLASS__;
        switch (count($args))
        {
            case 0:
                $class::$name();
                break;
            case 1:
                $class::$name($args[0]);
                break;
            case 2:
                $class::$name($args[0], $args[1]);
                break;
            case 3:
                $class::$name($args[0], $args[1], $args[2]);
                break;
            default:
                call_user_func_array(array($class, $name), $args);
                break;
        }
    }
}

That’s it; Hopefully, this might be useful to some of you…

Notes

  1. phpandme posted this
Top of Page