PHP FuncMocker
FuncMocker – Mocking PHP functions like a... punk rocker?, (*1)
![Software License][ico-license]
![Total Downloads][ico-downloads], (*2)
Allows you to overwrite (i.e., mock) global functions used within a given namespace for the purposes of testing., (*3)
There are two main use cases I developed this for:, (*4)
- When you are testing objects that call non-deterministic functions like
time() and rand(), and you need these
functions to return deterministic values for the sake of the test.
- When you are working with code where you have objects that must make calls to functions. This is pretty common in
legacy codebases that were not previously object-oriented in nature. For example, if you're project has a function
called
db_add() that you end up using in an object in your model layer, you might want to "mock" that function when
you are testing so you don't actually make calls to the database in your unit tests.
The simple technique behind this code is described in this blog post by Fabian Schmengler.
Basically, it involves taking advantage of PHP's namespace resolution rules., (*5)
Install
Via Composer, (*6)
``` bash
$ composer require jeremeamia/func-mocker, (*7)
## Usage
Let's you have a `RandomNumberGenerator` class in the namespace, `My\App`, that calls the global function `rand()`.
You could overwrite the usage of `rand()` for that particular namespace by using **FuncMocker**.
```php
use My\App\RandomNumberGenerator;
use FuncMocker\Mocker;
Mocker::mock('rand', 'My\App', function () {
return 5;
});
$rng = new RandomNumberGenerator(1, 10);
echo $rng->getNumber();
//> 5
echo $rng->getNumber();
//> 5
echo $rng->getNumber();
//> 5
Longer Example
Assume there is a class that uses the global function (e.g., time()) that you'd like to mock., (*8)
``` php
<?php, (*9)
namespace My\Crypto;, (*10)
use Psr\Http\Message\RequestInterface as Request;, (*11)
class Signer
{
// ..., (*12)
public function getStringToSign(Request $request)
{
return $request->getMethod() . "\n"
. time() . "\n"
. $request->getHeader('X-API-Operation')[0] . "\n"
. $request->getBody();
}
// ...
}, (*13)
Here is an example of a PHPUnit test that uses **FuncMocker** to mock `time()` to return a fixed value, making it much
easier to write a test.
```php
<?php
namespace My\App\Tests;
use FuncMocker\Mocker as FuncMocker;
use My\Crypto\Signer;
use Psr\Http\Message\RequestInterface as Request;
class SignerTest extends \PHPUnit_Framework_TestCase
{
// ...
public function testCanGetStringToSign()
{
// Mock the request with PHPUnit
$request = $this->getMock(Request::class);
$request->method('getMethod')->willReturn('POST');
$request->method('getHeader')->willReturn(['CREATE_THING']);
$request->method('getBody')->willReturn('PARAMS');
// Mock the call to PHP's time() function to give us a deterministic value.
FuncMocker::mock('time', 'My\Crypto', function () {
return 12345;
});
$signer = new Signer();
// Check to see that the string to sign is constructed how we would expect.
$this->assertEquals(
"POST\n12345\nCREATE_THING\nPARAMS",
$signer->getStringToSign()
);
}
// ...
}
Disabling and Re-Enabling
FuncMocker also lets you disable mocks you've setup, in case you need the function to behave normally in your some of
your tests., (*14)
$func = FuncMocker\Mocker::mock('time()', 'My\App', function () {
return 1234567890;
});
echo My\App\time();
//> 1234567890
$func->disable();
echo My\App\time();
//> 1458018866
$func->enable();
echo My\App\time();
// > 1234567890
Limitations
- The function to be mocked must be used in a namespace other than the global namespace.
- The function to be mocked must not be referenced using a fully-qualified name (e.g.,
\time()).
Testing
bash
$ composer test, (*15)
Credits
Alternatives
- Do it yourself. See the following articles:
-
php-mock/php-mock - Similar function mocking library I found afterwards.
-
Patchwork - More robust mocking library that can intercept calls to functions.
-
Atoum - Fullf-featured test framework with function mocking included.
License
The MIT License (MIT). Please see License File for more information., (*16)