Monkey patching in PHP

What is monkey patching?

I first learned about monkey patching via the Ruby community. Due to how the language worked, Rubyists tended to override dependencies in their tests at run-time, rather than using test doubles. Personally I find the ability to redefine any part of the language to be interesting. It certainly does make testing easier.

Now, PHP doesn't support the sort of metaprogramming that lends itself to making monkey patching easy. Sure, you used to be able to use the Runkit extension but these days, it's not being maintained. So, when we run into a situation where you need to redefine some functionality at run time, your options are limited.

When to use it?

Like I said before, it can be an alternative to using test doubles. But there are also some interesting scenarios where, due to both PHP's behaviour and the architecture of an application, we can make a change at run time.

At my current gig I was adding some functionality to verify some objects representing the prices of objects are configured correctly. One of my tests was to ensure that a specific exception was being triggered, and that meant creating a new exception object that extended a "loggable" one.

This is code that is being implemented as a plugin for WordPress and the logging object I needed to use had some very WordPress-specific functionality in it. But I didn't want to have to use all the WordPress-specific stack just for this test. On top of this, the logging object wasn't in a namespace that my PHPUnit tests could even see.

So what were my options? My first was to modify the Composer autoloading configuration and add the namespace to it. I then had a way to "force" logging into "test mode" but I wasn't entirely happy with it. The tests passed, the exception was being triggered, but I had worries in the back of my mind about whether or not we'd have to do something to the WordPress side of the application in order to support this.

A co-worker showed me how they were already overriding some things at run-time, so it was better to go with what was already working. Introducing uncertainty into our application was not the end goal. Here was their solution:

There was an existing test/bootstrap.php file so it was suggested to add a run-time check to see if the application was attempting to instantiate our logging object via an autoloader and then instead tell it to use a different one.

First, a replacement logger was created...


namespace Smartours\Log;

use Monolog\Handler\NullHandler;
use Monolog\Logger;

class Log
    public static function logger(): Logger
        $logger = new Logger('generic');
        $logger->pushHandler(new NullHandler());

        return $logger;

...and then we just included it in our bootstrap file.

require __DIR__ . '/bootstrap/SmartoursLog.php';

Now, my code that is calling an exception...that needs that base Log object...will use my fake instead.

The test passes and all is in order again.

What are some alternatives?

In a more testable world, what logging object the code is expecting to use could be done via a configuration file. We'd still be creating a fake logger, but the mechanism to load it would be different. Most "modern" PHP web application frameworks support the use of service locaters and they can be leveraged to make sure your tests have access to the dependencies they need.

In the future, if we ever needed to add tests for things like making sure the message the exception we throw shows up in the correct log file, this solution will have to adapt to those needs.

As always, everyone's testing situation is different and finding one that fits your need is more important than being perfect..

Categories: testing, PHP