Go Back

WP_Mock and WordPress Unit Testing with PHPUnit

Posted: 

I don't work in PHP too often but needed to write a plugin for a Redis cache project I was working on. It gave me the opportunity to dig into PHPUnit and how to test WordPress related code effectively.

The Goal

I wanted to be able to test the functionality of my plugin. Generally it's not too complicated. Whenever a post is saved, update the Redis cache for that item.

To write the test I ended up working from top to bottom on the function I was testing and determine what different states I wanted to test for.

What about WordPress specific functions

Right away I ran into the issue of how to deal with WordPress specific global functions.

I am using rest_do_request to get the REST response for a given post. But I don't want to test if rest_do_request is working, I just want to be able to specify what it's response should be. That's where mocking comes in.

WP_Mock

WP_Mock is a library that helps with mocking WordPress specific functionality. I'll be honest that I don't totally see the benefit to using WP_Mock over a lower level library, but if the few I did see made it worth it.

Under the hood it uses Mockery and just adds an abstraction layer on top.

I also found that enabling Patchwork solved some issues I was having. Again, not totally sure what additional functionality Patchwork adds, the documentation wasn't clear, but it seemed to be needed for what I was doing.

Mocking a class

Even though rest_do_request was essentially the first thing I needed to mock, it requires a WP_REST_Response as the parameter. Again, we don't want to test WP_REST_Response, just be able to tell it what its output should be. And I should note that it's not even available unless we spin up an entire WordPress instance. But that would also defeat the purpose of a unit test and would push closer to an integration or end to end test.

No classing mocking in WP_Mock

I couldn't figure out how to use WP_Mock to mock a class. That's where knowing it was based on mockery helped because I could use it without adding a new dependency.

$WP_REST_Request_Mock = Mockery::mock(WP_REST_Request::class);
$WP_REST_Request_Mock
    ->shouldReceive('__construct')
    ->with("GET", "/wp/v2/post/14");

Using the mockery static function mock we first create the mock. Then we also mock the __construct method on the class so we can expect what its parameters should be.

Mocking a function

Again, I still am not clear on the benefit of using WP_Mock for this vs straight up mockery, but I still used it and hopefully I'll figure out why its better.

WP_Mock::userFunction('rest_do_request')
    ->once()    
    ->andReturn($WP_REST_Response);

Now we've mocked rest_do_request, asserted it should be called just once and then telling it to return a WP_REST_Response object that was mocked (I'm not showing that here).

Now when we run our function, when it calls rest_do_request we can control exactly what goes in and out of that function and assert based on that.

Issue with add_action

Because I wanted to keep my plugin simple and just use functions instead of encapsulating in a class, as soon as I required my plugin file, it would run the add_action function.

Because add_action is so core to WordPress, I sometimes forget that it's actually a function. This is where WP_Mock has a special way of handling this.

setUpBeforeClass

I decided to run this code in the setUpBeforeClass lifecycle method that PHPUnit provides. That way we can use WP_Mock::expectActionAdded.

public static function setUpBeforeClass(): void
{
    WP_Mock::expectActionAdded('wp_after_insert_post', "Kaena\Content_Cache\cache_post_on_save", 10, 2);

    require_once __DIR__ . "/../cache-post-on-save.php";
}

It's similar to WP_Mock::userFunction but is specifically made to expect the add_action function to be run.

I create the mock before we require the function file so that add_action won't run until we've first mocked it.

What's more

That's about as far as I got with it. I believe there are some custom assertions that WP_Mock provides but I'd really like to know more of the benefits of using it.

Their documentation is clear and concise, but not very complete. But generally I was pleased with my experience and feel like my code is now covered I'll see if anything breaks.