How to use Brain Monkey to unit test WordPress functions without a full WordPress install?

So, you’ve got some nifty PHP functions for your WordPress project, and you want to make sure they’re working exactly as you expect. But, who has time for a full-blown WordPress setup just to run a few unit tests? If you’re nodding along, you’re in the right place. The good news is you can absolutely unit test your WordPress functions without needing a complete WordPress installation. And the tool that makes this possible for many developers is Brain Monkey.

Understanding the Challenge

Normally, WordPress functions rely on a whole ecosystem – the database, the core files, various hooks and filters, and a bunch of global variables. Trying to test these in isolation without that environment can feel like trying to fly a plane without an engine.

If you’re looking to enhance your development workflow, you might find it beneficial to explore related topics such as server migrations. For instance, understanding how to migrate your WordPress site efficiently can complement your unit testing efforts. You can read more about this in the article on migrating between CyberPanel servers, which provides valuable insights into server management that can help streamline your development process. Check it out here: Migrating to Another Server with CyberPanel.

So, How About Brain Monkey?

Brain Monkey is a PHP testing library that helps you mock or stub out those WordPress dependencies. Think of it like this: instead of needing the entire plane, you’re creating a really convincing simulator for the parts you need. This allows you to test your individual functions, almost like they are running within WordPress, but in a controlled and predictable way.

Getting Started with Brain Monkey

Before you can start mocking and stubbing, you’ll need to get Brain Monkey set up in your project. This usually involves using Composer, the go-to package manager for PHP.

Installing Brain Monkey

If you’re not already using Composer, you’ll want to do that first. If you are, it’s a straightforward command:

“`bash

composer require –dev brainmonkey/brainmonkey

“`

This command will download Brain Monkey and its dependencies and add them to your vendor directory. You’ll also want to make sure your autoload.php file is included in your test setup so that Brain Monkey is available to your test suite.

Setting Up Your Test Environment

You’ll need a PHP testing framework to actually run your tests. PHPUnit is the de facto standard for this in the PHP world. If you don’t have it, you can install it with Composer as well:

“`bash

composer require –dev phpunit/phpunit

“`

Your test files will typically live in a separate directory, often named tests or specs.

The Core Concept: Mocking and Stubbing

Brain Monkey shines when it comes to simulating WordPress’s behavior. This means you’ll be using its features to either mock or stub specific WordPress functions or objects.

What’s the Difference? Mock vs. Stub

It’s a common point of confusion, so let’s break it down simply:

  • Mocks: You use mocks when you want to verify that a specific method was called on an object, and that it was called with the correct arguments. Think of it as watching to see if someone did what you asked them to do, and how they did it.
  • Stubs: Stubs are used when you want to control the return value of a function or method. You’re essentially telling the simulated environment, “When this is called, just give me this specific answer.” This is useful for isolating your code and ensuring it behaves correctly regardless of what the real WordPress might return.

Brain Monkey provides tools for both.

Brain Monkey’s Key Features for WordPress Testing

Brain Monkey offers a set of functions that directly mirror common WordPress global functions and actions. Let’s dive into some of the most useful ones.

If you’re looking to enhance your WordPress development skills, you might find it helpful to explore how to use Brain Monkey for unit testing without needing a full WordPress installation. This approach can streamline your testing process significantly. Additionally, you may want to check out a related article on sending emails using CyberPanel, which provides insights into managing your server environment effectively. You can read more about it here.

Mocking WordPress Functions

This is where the magic happens. Brain Monkey allows you to “replace” WordPress functions with your own simulated versions within your test.

Mocking get_option()

One of the most common WordPress functions you’ll interact with is get_option(). Your custom code might fetch settings from the database using this. To test a function that uses get_option(), you’ll want to control what get_option() returns for specific keys.

Example: Testing a function that relies on a theme option

Let’s say you have a function like this:

“`php

// In your plugin/theme file

function my_plugin_get_api_key() {

return get_option(‘my_plugin_api_key’);

}

“`

To test my_plugin_get_api_key() without a WordPress install, you’d use Brain Monkey to mock get_option():

“`php

// In your unit test file

use Brain\Monkey\Functions;

class MyPluginApiTest extends TestCase { // Assuming you’re using PHPUnit

public function testGetApiKeyReturnsCorrectValue() {

$expectedApiKey = ‘sk_test_12345’;

// Mock get_option to return a specific value for a specific key

Functions\when(‘get_option’)->alias(function($name) use ($expectedApiKey) {

if ($name === ‘my_plugin_api_key’) {

return $expectedApiKey;

}

return false; // Or whatever the default behavior would be

});

// Now, call your function. Brain Monkey intercepts get_option.

$actualApiKey = my_plugin_get_api_key();

$this->assertEquals($expectedApiKey, $actualApiKey);

}

public function testGetApiKeyReturnsFalseWhenOptionNotSet() {

// Mock get_option to return false for an unset option

Functions\when(‘get_option’)->alias(function($name) {

return false; // Simulate the option not being set

});

$actualApiKey = my_plugin_get_api_key();

$this->assertFalse($actualApiKey);

}

}

“`

  • Functions\when('get_option'): This tells Brain Monkey you want to control the behavior of the get_option function.
  • ->alias(function(...) { ... }): This is where you define what get_option should do when it’s called. You can create a closure (an anonymous function) that checks the $name argument and returns a predefined value. This is incredibly powerful for simulating different scenarios.
  • $this->assertEquals(...): This is a standard PHPUnit assertion to check if two values are equal.

Mocking update_option()

Similarly, if your function modifies options, you can mock update_option() to verify that it’s called correctly.

Example: Testing a function that saves a setting

Consider a function like this:

“`php

// In your plugin/theme file

function my_plugin_save_setting( $value ) {

return update_option( ‘my_plugin_setting’, $value );

}

“`

Here’s how you might test it:

“`php

// In your unit test file

use Brain\Monkey\Functions;

class MyPluginSettingSaveTest extends TestCase {

public function testSaveSettingUpdatesOption() {

$settingValue = ‘new_value’;

$expectedReturn = true; // update_option usually returns true on success

// Mock update_option to simulate a successful save

Functions\when(‘update_option’)->alias(function($optionName, $value) use ($expectedReturn) {

// You can add assertions here to verify the arguments passed

$this->assertEquals(‘my_plugin_setting’, $optionName);

$this->assertEquals($settingValue, $value);

return $expectedReturn;

});

$result = my_plugin_save_setting( $settingValue );

$this->assertTrue($result);

}

}

“`

In this case, you’re not just controlling the return value, but also potentially asserting that update_option was called with the correct parameters. This is a more advanced usage of mocking for verification.

Mocking wp_remote_get() and wp_remote_post()

Many WordPress plugins and themes interact with external APIs. These are usually handled via wp_remote_get() and wp_remote_post(). Testing your network requests becomes much easier when you can control the responses.

Example: Testing an API data fetcher

Let’s say you have a function to fetch data from an external service:

“`php

// In your plugin/theme file

function my_plugin_fetch_remote_data( $url ) {

$response = wp_remote_get( $url );

if ( is_wp_error( $response ) ) {

return null;

}

$body = wp_remote_retrieve_body( $response );

$data = json_decode( $body, true );

return $data;

}

“`

Testing this involves mocking wp_remote_get() and potentially wp_remote_retrieve_body().

“`php

// In your unit test file

use Brain\Monkey\Functions;

use GusteauG\BrainMonkey\Mock\HttpRequest\Response; // Need a way to mock WP_Error and response body

class MyPluginApiFetchTest extends TestCase {

public function testFetchRemoteDataReturnsParsedJson() {

$apiUrl = ‘https://example.com/api/data’;

$mockResponseData = [‘status’ => ‘success’, ‘data’ => [‘item1’, ‘item2’]];

$mockResponseBody = json_encode($mockResponseData);

// Mock wp_remote_get to return a successful response

Functions\when(‘wp_remote_get’)->alias(function($url) use ($mockResponseBody) {

$this->assertEquals($apiUrl, $url); // Verify the URL was called

return new Response(200, [‘Content-Type’ => ‘application/json’], $mockResponseBody);

});

// Mock wp_remote_retrieve_body to return the body from our mocked response

Functions\when(‘wp_remote_retrieve_body’)->alias(function($response) use ($mockResponseBody) {

// In a real scenario, you’d check if $response is the mocked one

return $mockResponseBody;

});

$data = my_plugin_fetch_remote_data( $apiUrl );

$this->assertIsArray($data);

$this->assertEquals($mockResponseData, $data);

}

public function testFetchRemoteDataHandlesWpError() {

$apiUrl = ‘https://example.com/api/error’;

// Mock wp_remote_get to return a WP_Error object

Functions\when(‘wp_remote_get’)->alias(function($url) {

return new \WP_Error(‘api_failed’, ‘Something went wrong’);

});

// We don’t need to mock wp_remote_retrieve_body here as the WP_Error path is taken first.

// Brain Monkey typically handles the order of execution in such cases.

$data = my_plugin_fetch_remote_data( $apiUrl );

$this->assertNull($data);

}

}

“`

Note: The Response class might need to be implemented or found within a Brain Monkey addon if not directly available. The core idea is to simulate the return structure of wp_remote_get.

This shows how you can simulate both successful and error responses from external APIs by controlling what wp_remote_get returns.

Working with WordPress Hooks (Actions and Filters)

Your WordPress code is likely full of hooks. Brain Monkey makes it possible to test functions that are hooked into WordPress, or to test functions that add hooks.

Mocking Actions (do_action())

If your function executes a do_action(), you can mock do_action() to ensure it’s called correctly. More commonly, you’ll be testing a function that runs when an action is fired. In this scenario, you’ll use Brain Monkey to simulate the actual firing of the action, and then your test will verify the outcome.

Example: Testing a function attached to init

Let’s say you have a function that registers a custom post type, hooked into init:

“`php

// In your plugin/theme file

function my_plugin_register_custom_post_type() {

register_post_type( ‘book’, array(

‘labels’ => array(‘name’ => ‘Books’),

‘public’ => true,

‘supports’ => array(‘title’, ‘editor’),

) );

}

add_action( ‘init’, ‘my_plugin_register_custom_post_type’ );

“`

To test this, you need to mock register_post_type() and then simulate the init action.

“`php

// In your unit test file

use Brain\Monkey\Functions;

use Brain\Monkey\Actions;

class MyPluginPostTypeTest extends TestCase {

public function testRegisterCustomPostTypeRegistersCorrectly() {

$postTypeName = ‘book’;

$postTypeArgs = array(

‘labels’ => array(‘name’ => ‘Books’),

‘public’ => true,

‘supports’ => array(‘title’, ‘editor’),

);

// Mock register_post_type to verify it’s called

Functions\when(‘register_post_type’)->alias(function($name, $args) use ($postTypeName, $postTypeArgs) {

$this->assertEquals($postTypeName, $name);

$this->assertEquals($postTypeArgs, $args);

// The actual registration happens in WordPress, here we just verify it’s called

});

// Simulate the ‘init’ action firing. This will trigger your hooked function.

Actions\perform_action(‘init’);

// If your function had side effects you could assert them here,

// but often verifying the call to register_post_type is enough.

}

}

“`

The key here is Actions\perform_action('init'). Brain Monkey knows about your add_action calls (or can be configured to) and will execute the associated function when you tell it to perform that action.

Mocking Filters (apply_filters())

Filters are used to modify data. If your function applies a filter, or is intended to run when a filter is applied, you can test this.

Example: Modifying post content with a filter

Suppose you have a function that adds a disclaimer to post content:

“`php

// In your plugin/theme file

function my_plugin_add_disclaimer_to_content( $content ) {

// Ensure this filter is only applied to single posts for demonstration

if ( is_singular() ) {

$disclaimer = ‘

Disclaimer: This content is for informational purposes only.

‘;

return $content . $disclaimer;

}

return $content;

}

add_filter( ‘the_content’, ‘my_plugin_add_disclaimer_to_content’ );

“`

To test this, you’ll mock is_singular() and then simulate the filter.

“`php

// In your unit test file

use Brain\Monkey\Functions;

use Brain\Monkey\Filters;

class MyPluginContentFilterTest extends TestCase {

public function testAddDisclaimerToContentForSingularPost() {

$originalContent = ‘

Original post content.

‘;

$expectedContent = $originalContent . ‘

Disclaimer: This content is for informational purposes only.

‘;

// Mock is_singular to return true, simulating a single post view

Functions\when(‘is_singular’)->returnArg(0); // Or return true;

// We don’t need to mock apply_filters itself here because we are

// testing the behavior of the filter hook. We’ll use Filters\run_filter.

$actualContent = Filters\run_filter(‘the_content’, $originalContent);

$this->assertEquals($expectedContent, $actualContent);

}

public function testAddDisclaimerToContentForArchivePage() {

$originalContent = ‘

Archive content snippet.

‘;

// Mock is_singular to return false, simulating an archive page

Functions\when(‘is_singular’)->returnArg(1); // Or return false;

$actualContent = Filters\run_filter(‘the_content’, $originalContent);

$this->assertEquals($originalContent, $actualContent); // No disclaimer should be added

}

}

“`

  • Functions\when('is_singular')->returnArg(0): A shortcut for return true;.
  • Functions\when('is_singular')->returnArg(1): A shortcut for return false;.
  • Filters\run_filter('the_content', $originalContent): This is the core of testing a filter. You tell Brain Monkey to execute all functions hooked to the_content filter using $originalContent as the initial value.

Handling WordPress Globals and Objects

WordPress relies heavily on global variables (like $post, $wpdb, $wp_query). Brain Monkey provides ways to manage these for your tests.

Mocking $wpdb

If your functions interact directly with the database using $wpdb, you’ll need to mock its methods.

Example: Testing a function that queries the database

“`php

// In your plugin/theme file

function my_plugin_get_user_email( $user_id ) {

global $wpdb;

return $wpdb->get_var( $wpdb->prepare( “SELECT meta_value FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key = %s”, $user_id, ‘user_email’ ) );

}

“`

Testing this requires mocking $wpdb->get_var and $wpdb->prepare.

“`php

// In your unit test file

use Brain\Monkey\Functions;

use Brain\Monkey\Globals;

class MyPluginUserEmailTest extends TestCase {

public function testGetUserEmailReturnsCorrectEmail() {

$userId = 1;

$expectedEmail = ‘test@example.com’;

$mockQuery = “SELECT meta_value FROM wp_usermeta WHERE user_id = 1 AND meta_key = ‘user_email'”;

// Mock $wpdb->prepare to return the SQL string for assertion

Functions\when(‘prepare’)->alias(function($query, …$args) use ($mockQuery) {

// In a real mock, you’d actually check args. For simplicity here, we assume it works.

return $mockQuery;

});

// Mock $wpdb->get_var to return the email for our specific query

Functions\when(‘get_var’)->alias(function($query) use ($mockQuery, $expectedEmail) {

if ($query === $mockQuery) {

return $expectedEmail;

}

return null;

});

// Set up the global $wpdb object.

// Note: We’re mocking the global functions ‘prepare’ and ‘get_var’

// which are often aliased to $wpdb->prepare and $wpdb->get_var respectively.

// If your code directly uses $wpdb->prepare, you may need to mock the object itself.

// This is a common simplification for testing.

$actualEmail = my_plugin_get_user_email($userId);

$this->assertEquals($expectedEmail, $actualEmail);

}

}

“`

  • Globals\set( 'wpdb', new MockWpdbObject() ): If you need to mock methods of the $wpdb object directly (e.g., $wpdb->insert(), $wpdb->query()), you’d replace the global function mocks with mocking the object itself. Brain Monkey and other libraries might provide helper classes for this, or you’d create a simple mock object with the methods you need. In the example above, we’ve simplified by mocking the equivalent global functions prepare and get_var which WordPress often maps for convenience.

Mocking $post and $wp_query

When dealing with posts, pages, and the main query, you’ll often interact with $post and $wp_query.

Example: Testing a function that uses $post->ID

“`php

// In your plugin/theme file

function my_plugin_get_current_post_id() {

global $post;

if ( $post ) {

return $post->ID;

}

return null;

}

“`

Testing this involves setting up the global $post object.

“`php

// In your unit test file

use Brain\Monkey\Globals;

use Brain\Monkey\Functions; // Potentially for get_queried_object_id etc.

class MyPluginPostIdTest extends TestCase {

public function testGetCurrentPostIdReturnsCorrectId() {

// Create a mock post object

$mockPost = new \stdClass();

$mockPost->ID = 42;

$mockPost->post_title = ‘Test Post’; // Add other properties as needed by your code

// Set the global $post variable

Globals\set( ‘post’, $mockPost );

$actualId = my_plugin_get_current_post_id();

$this->assertEquals(42, $actualId);

}

public function testGetCurrentPostIdReturnsNullWhenNoPost() {

// Unset the global $post variable or set it to null

Globals\set( ‘post’, null );

$actualId = my_plugin_get_current_post_id();

$this->assertNull($actualId);

}

}

“`

  • Globals\set( 'post', $mockPost ): This is how you inject your mock data into the global scope for your test. You can use a simple \stdClass object and add properties to it as needed.

Advanced Techniques and Best Practices

While Brain Monkey is powerful, there are a few things to keep in mind for efficient and maintainable testing.

Setting Up a Test Class

For any non-trivial project, you’ll want to organize your Brain Monkey setup into a base test class that your individual test cases can extend. This avoids repeating setup code in every test.

Example: A Base Test Class

“`php

// In your tests/BaseTestCase.php file

namespace MyPlugin\Tests;

use PHPUnit\Framework\TestCase;

use Brain\Monkey;

class BaseTestCase extends TestCase {

protected function setUp(): void {

parent::setUp(); // Important to call the parent setUp

Monkey\setUp(); // Initialize Brain Monkey for this test

}

protected function tearDown(): void {

Monkey\tearDown(); // Clean up Brain Monkey after this test

parent::tearDown(); // Important to call the parent tearDown

}

}

“`

Then, in your actual test files, you’d extend this class:

“`php

// In your tests/MyPluginApiTest.php file

namespace MyPlugin\Tests;

use MyPlugin\Tests\BaseTestCase; // Import your base class

class MyPluginApiTest extends BaseTestCase { // Extend your base class

// … your tests …

}

“`

  • Monkey\setUp() and Monkey\tearDown(): These are crucial for ensuring Brain Monkey is properly initialized before each test runs and cleaned up afterwards. This prevents test pollution.

When to Use Brain Monkey vs. a Full WordPress Install

  • Use Brain Monkey when: You’re testing individual functions, small units of logic, or components that have well-defined WordPress dependencies. This is great for rapid development and ensuring core logic is sound without the overhead of a full environment.
  • Consider a full WordPress install (often via WordPress.org’s wp_test_case or WP-CLI’s test runner) when: You need to test complex interactions with the WordPress core, plugins, themes, HTTP requests, or database operations that are difficult to mock accurately. This is more for integration testing.

Mocking WP_Error Objects

WordPress often returns WP_Error objects for failures. You’ll need to mock these when testing error conditions.

Example: Returning a WP_Error

“`php

use Brain\Monkey\Functions;

Functions\when(‘some_function_that_can_fail’)->alias(function() {

return new \WP_Error(‘error_code’, ‘A user-friendly message’);

});

“`

Managing Your vendor Directory

When you install Brain Monkey and PHPUnit, they go into your vendor directory. You should not commit this directory to your version control system. Instead, you’ll have a composer.json file, and any developer on your project can run composer install to get all the necessary dependencies.

Conclusion: Empowering Your WordPress Development Workflow

Unit testing functions without a full WordPress install is not only possible but often highly beneficial. Tools like Brain Monkey provide the flexibility to isolate your code, test edge cases, and ensure reliability without the friction of a complete environment setup. By understanding how to mock functions, hooks, and globals, you can build more robust WordPress plugins and themes with greater confidence. It’s about making your development process smoother and your codebase more dependable.