PHPUnit tests of code containing internal PHP functions

In PHPUnit-testing, it is often encountered a code which depends on internal PHP functions like phpversion().

PHPUnit tests of code containing internal PHP functions


In PHPUnit-testing, it is often encountered a code which depends on internal PHP functions like phpversion(). Approach to testing is described in the current article.

Let us consider as a simple examle a class which checks current PHP version and tells if it satisfies requirements.

<?php
/**
 * Class to check requirements of the plugin.
 *
 * @package sample-plugin
 */

/**
 * Class Sample_Requirements
 */
class Sample_Requirements {

	/**
	 * Check php version.
	 *
	 * @return bool
	 */
	public function is_php_version_required() {
		if ( version_compare( '5.6', phpversion(), '>' ) ) {
			$this->php_requirement_message();

			return false;
		}

		return true;
	}

	/**
	 * Show notice with php requirement.
	 */
	public function php_requirement_message() {
		// Some code to show the message.
	}
}

How to test method is_php_version_required()? We can, of course, write an auxiliary method, for instance:

	public function phpversion() {
		return phpversion();
	}

and call it: $this->phpversion(). And, in tests, mock this method. But it looks quite heavy.

There is a way to replace internal PHP functions. It is provided by the lucatume/function-mocker library. It uses antecedent/patchwork library, which makes main work by monkey patch.

We should add use statement and FunctionMocker initialization to bootstrap.php:

use tad\FunctionMocker\FunctionMocker;

// Main bootrstrap code...

FunctionMocker::init(
	[
		'whitelist'             => [
			realpath( PLUGIN_PATH . '/includes' ),
		],
		'blacklist'             => [
			realpath( PLUGIN_PATH ),
		],
		'redefinable-internals' => [ 'phpversion' ],
	]
);

Here whitelist is the path to the folder where are tested classes. In this folder patchwork will redefine functions. blacklist is the path to the root plugin folder, here no replacement will be made (except whitelist folder). It is important, as some libraries (for instance, Mockery for unit testing) do not work otherwise. redefinable-internals is list of functions which will be replaced during testing.

And now result of internal function can be redefined “on the fly”, right during test execution:

<?php
/**
 * Test_Sample_Requirements class file
 *
 * @package sample-plugin
 */

use PHPUnit\Framework\TestCase;
use tad\FunctionMocker\FunctionMocker;

/**
 * Class Test_Sample_Requirements
 *
 * @group requirements
 */
class Test_Sample_Requirements extends TestCase {

	/**
	 * Test if is_php_version_required().
	 */
	public function test_is_php_version_required() {
		$subject = new Sample_Requirements();

		FunctionMocker::replace( 'phpversion', '5.5' );
		$this->assertFalse( $subject->is_php_version_required() );

		FunctionMocker::replace( 'phpversion', '5.6' );
		$this->assertTrue( $subject->is_php_version_required() );
	}
}

Besides, FunctionMocker provides a number of additional capabilities: replacing of static methods, spying of methods, replacing of global variables, and methods of global objects ( for instance, $wpdb->get_row ), etc.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.