Blog de Nathan Story

Mocking Functions in PHP Unit Tests

| Comments

If you are testing a namespaced class, here is a little trick you can use to mock out dependencies on global functions (this is a very common need given that most of the PHP standard library is functions in the global namespace!).

Suppose we have the following class we want to test:

<?php
namespace Example;
class Example {
    public function run() {
        // forward the call to the mocked function
        system('echo "Hello, World!"');
    }
}

We want to confirm that the run() method above calls system(), but we don’t want to actually execute the echo command i.e. we want to mock the function.

The below test case accomplishes this. I’m using the excellent Mockery library to do the mocking – check it out if you aren’t already familiar with it.

<?php
namespace Example;

use \Mockery as m;

// we can do this because this function is being defined in the
// Example namespace, and, so, this is not overwriting the built-in function,
// it's merely obscuring it
function system($cmd) {
    return ExampleTest::$functions->system($cmd);
}

class ExampleTest extends \PHPUnit_Framework_TestCase {

    public static $functions;
    private $example;

    public function setUp() {
        self::$functions = m::mock();
        $this->example = new Example();
    }

    public function tearDown() {
        // see Mockery's documentation for why we do this
        m::close();
    }

    function testRun() {
        // this creates an expectation that the system() function will be called
        // with the given parameters
        self::$functions->shouldReceive('system')->with('echo "Hello, World!"')->once();
        $this->example->run();
    }
}

As you can see above, I’m defining a system() function in the class-under-test’s namespace (in this case \Example). When run() calls system(), it’s calling the function in its namespace instead of the global function.

This “imposter” function forwards all calls to the static ExampleTest::$functions mock object. This allows me to use the tools Mockery provides for setting expectations, etc.

Comments