Welcome to Abdul Malik Ikhsan's Blog

Monkey Patch PHP Quit Statement with Patchwork

Posted in Tutorial PHP by samsonasik on January 18, 2016

If your job is make tests for legacy app that has exit() or die(); everywhere, and you don’t have privilege to refactor them, make tests for them may be hard as the test aborted when the quit statements executed in the tests. To test them, we need to redefine the user-defined functions and methods at runtime, and there is a lib for that, it is named Patchwork.

We can run command:

$ composer require antecedent/patchwork:*

to get the Patchwork dependency.

Let’s see how it can work, let say, we have a class:

namespace App;

class MyClass
{
    public function foo($arg)
    {
        if ($arg === 1) {
            return true;
        }

        exit('app exit.');
    }
}

Seeing the MyClass::foo, we can only tests if the $arg is equal then 1, otherwise, we need to redefine it, and there is a Patchwork\replace() method for it by call like the following:

replace(MyClass::class. '::foo', function($arg) {
    if ($arg === 1) {
        pass();
    }
    return 'app exit.';
});

The pass() method will call original method functionality if $arg === 1 as that not return quit statement, otherwise we redefine to return string with value = ‘app exit’.

We can define in our unit test like this MyClassTest class:

namespace AppTest;

use PHPUnit_Framework_TestCase;
use App\MyClass;
use function Patchwork\pass;
use function Patchwork\replace;
use function Patchwork\undoAll;

class MyClassTest extends PHPUnit_Framework_TestCase
{
    private $myclass;

    protected function setUp()
    {
        replace(MyClass::class. '::foo', function($arg) {
            if ($arg === 1) {
                pass();
            }
            return 'app exit.';
        });

        $this->myclass = new MyClass;
    }

    protected function tearDown()
    {
        undoAll();
    }
}

We can call the replace it in setUp(), and to undo, we can call undoAll() in tearDown(). And now, we can add the tests into MyClassTest tests :

// ...

    public function provideFoo()
    {
        return [
            [1, true],
            [0, 'app exit.'],
        ];
    }

    /**
     * @dataProvider provideFoo
     */
    public function testFoo($arg, $result)
    {
        $this->assertSame($result, $this->myclass->foo($arg));
    }

// ...

References to read:
http://kahlan.readthedocs.org/en/latest/monkey-patching/
http://antecedent.github.io/patchwork/
http://afilina.com/testing-methods-that-make-static-calls/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: