Mock built-in PHP functions (e.g. time(), exec() or rand())

Overview

PHP-Mock: mocking built-in PHP functions

PHP-Mock is a testing library which mocks non deterministic built-in PHP functions like time() or rand(). This is achieved by PHP's namespace fallback policy:

PHP will fall back to global functions […] if a namespaced function […] does not exist.

PHP-Mock uses that feature by providing the namespaced function. I.e. you have to be in a non global namespace context and call the function unqualified:

namespace foo;

$time = time(); // This call can be mocked, a call to \time() can't.

Requirements and restrictions

  • Only unqualified function calls in a namespace context can be mocked. E.g. a call for time() in the namespace foo is mockable, a call for \time() is not.

  • The mock has to be defined before the first call to the unqualified function in the tested class. This is documented in Bug #68541. In most cases, you can ignore this restriction but if you happen to run into this issue you can call Mock::define() before that first call. This would define a side effectless namespaced function which can be enabled later. Another effective approach is running your test in an isolated process.

Alternatives

If you can't rely on or just don't want to use the namespace fallback policy, there are alternative techniques to mock built-in PHP functions:

  • PHPBuiltinMock relies on the APD extension.

  • MockFunction is a PHPUnit extension. It uses the runkit extension.

  • UOPZ is a Zend extension which allows, among others, renaming and deletion of functions.

  • vfsStream is a stream wrapper for a virtual file system. This will help you write tests which covers PHP stream functions (e.g. fread() or readdir()).

Installation

Use Composer:

composer require --dev php-mock/php-mock

Usage

You don't need to learn yet another API. PHP-Mock has integrations for these testing frameworks:

Note: If you plan to use one of the above mentioned testing frameworks you can skip reading any further and just go to the particular integration project.

PHP-Mock API

You find the API in the namespace phpmock.

Create a Mock object. You can do this with the fluent API of MockBuilder:

After you have build your Mock object you have to call enable() to enable the mock in the given namespace. When you are finished with that mock you should disable it by calling disable() on the mock instance.

This example illustrates mocking of the unqualified function time() in the namespace foo:

namespace foo;

use phpmock\MockBuilder;

$builder = new MockBuilder();
$builder->setNamespace(__NAMESPACE__)
        ->setName("time")
        ->setFunction(
            function () {
                return 1417011228;
            }
        );
                    
$mock = $builder->build();

// The mock is not enabled yet.
assert (time() != 1417011228);

$mock->enable();
assert (time() == 1417011228);

// The mock is disabled and PHP's built-in time() is called.
$mock->disable();
assert (time() != 1417011228);

Instead of setting the mock function with MockBuilder::setFunction() you could also use the existing FixedValueFunction:

namespace foo;

use phpmock\MockBuilder;
use phpmock\functions\FixedValueFunction;

$builder = new MockBuilder();
$builder->setNamespace(__NAMESPACE__)
        ->setName("time")
        ->setFunctionProvider(new FixedValueFunction(1417011228));

$mock = $builder->build();

Reset global state

An enabled mock changes global state. This will break subsequent tests if they run code which would call the mock unintentionally. Therefore you should always disable a mock after the test case. You will have to disable the created mock. You could do this for all mocks by calling the static method Mock::disableAll().

Mock environments

Complex mock environments of several mocked functions can be grouped in a MockEnvironment:

SleepEnvironmentBuilder

The SleepEnvironmentBuilder builds a mock environment where sleep() and usleep() return immediatly. Furthermore they increase the amount of time in the mocked date(), time() and microtime():

namespace foo;

use phpmock\environment\SleepEnvironmentBuilder;

$builder = new SleepEnvironmentBuilder();
$builder->addNamespace(__NAMESPACE__)
        ->setTimestamp(1417011228);

$environment = $builder->build();
$environment->enable();

// This won't delay the test for 10 seconds, but increase time().        
sleep(10);

assert(1417011228 + 10 == time());

If the mocked functions should be in different namespaces you can add more namespaces with SleepEnvironmentBuilder::addNamespace()

Spies

A Spy gives you access to the function invocations. Spy::getInvocations() gives you access to the arguments and return value.

As a Spy is a specialization of Mock it behaves identically. However you could ommit the third constructor parameter callable $function which would then create a spy using the existing function. E.g. a new Spy(__NAMESPACE__ , "rand") would create a spy which basically proxies PHP's built-in rand():

namespace foo;

use phpmock\spy\Spy;

function bar($min, $max) {
    return rand($min, $max) + 3;
}

$spy = new Spy(__NAMESPACE__, "rand");
$spy->enable();

$result = bar(1, 2);

assert ([1, 2]  == $spy->getInvocations()[0]->getArguments());
assert ($result == $spy->getInvocations()[0]->getReturn() + 3);

License and authors

This project is free and under the WTFPL. Responsable for this project is Markus Malkusch [email protected]. This library was inspired by Fabian Schmengler's article PHP: “Mocking” built-in functions like time() in Unit Tests.

Donations

If you like PHP-Mock and feel generous donate a few Bitcoins here: 1335STSwu9hST4vcMRppEPgENMHD2r1REK

Comments
  • missing

    missing "tests" folder

    Hi,

    i have the Problem, that the Tests are missing if i install the version 2.1 via composer. If i look in your repository in github, it seems composer.json are the reason for that:

    "archive": { "exclude": ["/tests"] }

    Problem here is the Code in vendor/php-mock/php-mock/autoload.php where you create class alias for a class who does not exists in the project. I cant really use your package with this behaviour.

    bug 
    opened by mastercad 22
  • Correct the namespace of MockNamespaceTest

    Correct the namespace of MockNamespaceTest

    The namespace of MockNamespaceTest class did not comply with psr-4 autoloading standard. Leaving the class with incorrect namespace would cause it to not be autoloaded in Composer v2.0.

    bug 
    opened by hhovakimyan 9
  • Introduces hack to pass variabels by reference

    Introduces hack to pass variabels by reference

    When mocking system-functions that expect variables to be passed by reference, this hack allows to do so. This might f.i. help mocking sort-functions or exec or passthru

    opened by heiglandreas 9
  • Doesn't call mocked function from a called function!

    Doesn't call mocked function from a called function!

    Hi all, I'm new to PHP unit testing so please advise if I'm dong anything wrong. This is the scenario:

    In our main code, there is a function (say b) that calls another function (say a). I want to test b() without having to test a() so want to make a mock of a. However in my test call when I can b() it still calls the original a, not my mock. Here is my code:

    In my test file:

    <?php
    namespace AA\BB\CC;
    use PHPUnit\Framework\TestCase;
    use phpmock\MockBuilder;
    
    class XYZ extends TestCase {
            private $a_mock;
    
            public function testb() {
                    $a_builder    = new MockBuilder();
                    $a_builder->setNamespace(__NAMESPACE__)
                            ->setName("a")
                            ->setFunction(
                                function ($str) {
                                    return $str;
                                }
                            );
    
                    $this->a_mock = $a_builder->build();
                    $this->a_mock->enable();
                    $result = b("Hello");
                            // NOTE: If I call a() here, then the mock version is called
                            // but if b() is called, it calls the other a() defined below
                    $this->a_mock->disable();
            }
    }
    

    In our code base (I don't see any namespaces defined here):

    function a($str) {
            // do something
    }
    
    function b($str) {
            return a($str);
    }
    
    question 
    opened by asra-baig 5
  • MockEnvironment extends Mock to be usable with MockDisabler.

    MockEnvironment extends Mock to be usable with MockDisabler.

    Hi Markus,

    Thank you for accepting my previous PR!

    I would find useful if MockEnvironment extends Mock. This would write the following:

    class MyTest extends \PHPUnit_Framework_TestCase
    {
        public function setUp()
        {
            $builder = new SleepEnvironmentBuilder();
            $builder->setNamespace('My\\Namespace')
                ->setTimestamp(strtotime('2030-01-01 00:00:00'));
            $mock = $builder->build();
            $mock->enable();
            $this->getTestResultObject()->addListener(new MockDisabler($mock));
            // ...
    

    Indeed, MockDisabler's constructor requires a Mock instance.

    Regards, Geoffroy

    opened by geoffroy-aubry 3
  • Global namespace mocks

    Global namespace mocks

    The source article referenced 7 years ago when PHP5.3 was new used the standard non-bracketed namespace declaration. If you use the bracketed one here you actually unlock global namespace mocks as well.

    Specifically: \function_call() becomes mockable.

    enhancement 
    opened by jwommack 2
  • Add PHPUnit 7.4 compatibility

    Add PHPUnit 7.4 compatibility

    Fatal error: Declaration of phpmock\phpunit\MockObjectProxy::__phpunit_verify() must be compatible with PHPUnit_Framework_MockObject_MockObject::__phpunit_verify(bool $unsetInvocationMocker = true) in vendor/php-mock/php-mock-phpunit/classes/MockObjectProxy.php on line 17
    
    opened by willemstuursma 2
  • PHP 7 Return Types

    PHP 7 Return Types

    It looks like Mockery is not working with the new PHP 7 Return Types.

    If I have an method with a return type:

    <?php
    
    namespace Game\Repository;
    
    use Game\Entity\UserInterface;
    
    Interface UserRepositoryInterface
    {
        public function createNewUser($username, $password, $email):UserInterface;
    }
    

    And I mock it: $userDummy = Mockery::mock('Game\Entity\UserInterface');

    I get this error:

    PHP Fatal error:  Declaration of 
    Mockery_2_Game_Repository_UserRepositoryInterface::createNewUser($username, $password, $email) 
    must be compatible with 
    Game\Repository\UserRepositoryInterface::createNewUser($username, $password, $email): Game\Entity\UserInterface 
    in ***\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php(16) : eval()'d code on line 25                
    

    If I take the return type off, Mockery works again. Mockery's php version says ">=5.5" so I was hoping it would work with 7 :)

    opened by jmauerhan 2
  • Can I replace static method?

    Can I replace static method?

    Hi, guy!

    I want to replace my static method by using setName. But I think It can't be static method, right? If YES, I want to have some advice. there is sample code below. I'll wait for answer.

    thanks!

        // foo\bar::doSomething() is replaced with 'Yepppp!'
        $builder = new \phpmock\MockBuilder();
        $builder->setNamespace('foo')
                ->setName('doSomething')
                ->setFunction(function () {
                    return 'Yepppp!!';
                });
        $mock = $builder->build();
        $mock->enable();
    
    opened by blackpost38 2
  • Need help to bypass Bug #68541

    Need help to bypass Bug #68541

    This is a handy library, and i'm using it to replace a old test case, which originally with namespace hack like:

    namespace Test;
    class ATest {}
    
    namespace App;
    function extension_loaded() {}
    

    The test purpose is to simulate PHP extension load status, and throw Exception when failed.

    For using php-mock, i create a trait like this:

    trait ExtensionLoadedMockTrait
    {
        /** @type bool */
        public static $extensionLoaded = true;
    
    
        /**
         * @param   string  $namespace
         * @return  Mock
         */
        public function buildExtensionLoadedMock($namespace)
        {
            $function = 'extension_loaded';
            $mockContainer = MockContainer::getInstance();
            $mock = $mockContainer->getMock($namespace, $function);
    
            if (is_null($mock)) {
                $mock = (new MockBuilder())
                    ->setNamespace($namespace)
                    ->setName($function)
                    ->setFunction(function($ext) {
                        return self::$extensionLoaded &&
                            \extension_loaded($ext);
                    })
                    ->build();
    
                $mock->define();
    
                $mockContainer->registerMock($namespace, $function, $mock);
            }
    
            return $mock;
        }
    }
    

    The source code of MockContainer is omit. In test case, use

    $this->buildExtensionLoadedMock('Foo\Bar')->enable();
    

    to enable or disable mock.

    This test works fine when run by PHPUnit with single file given, and with directory given, but fails when run PHPUnit without any parameter:

    // Ok
    phpunit path/to/test/case.php
    // Ok
    phpunit path/to/tests/
    // Fail
    phpunit
    

    Why ?

    As you mentioned about Bug #68541, the mock need to define before native PHP functions. So I make a special test case name like 'Lib\Aaa\Aaa.php', and I'm sure it will run before all other test cases, but the mock mechanism still not work, the mocked function are not triggered at all.

    Run single file are successful, i think the code is correct, but failed when batch run confuse me, do you have any tips ? Thanks.

    PHP: 5.5.9 PHPUnit: 4.5.0 Os: Ubuntu 14.04 Trusty

    opened by fwolf 2
  • Not working with die() nor exit()

    Not working with die() nor exit()

    Hi,

    I hoped to mock die() function (or exit), seems not possible. I got that :

    namespace cool;
    
    use phpmock\\generator\\MockFunctionGenerator;
    
    function exit()
    {
        $arguments = [];
    
        $variadics = \\array_slice(\\func_get_args(), \\count($arguments));
        $arguments = \\array_merge($arguments, $variadics);
    
        return MockFunctionGenerator::call(
            \'exit\',
            \'cool\\exit\',
            $arguments
        );
    }
    

    PHP Parse error: syntax error, unexpected 'exit' (T_EXIT), expecting '(' in \vendor\php-mock\php-mock\classes\generator\MockFunctionGenerator.php(67) : eval()'d code on line 5

    invalid 
    opened by guitarneck 1
Releases(2.3.1)
Owner
null
SimpleTest is a framework for unit testing, web site testing and mock objects for PHP

SimpleTest SimpleTest is a framework for unit testing, web site testing and mock objects for PHP. Installation Downloads All downloads are stored on G

SimpleTest 147 Jun 20, 2022
vfsStream is a stream wrapper for a virtual file system that may be helpful in unit tests to mock the real file system. It can be used with any unit test framework, like PHPUnit or SimpleTest.

vfsStream vfsStream is a stream wrapper for a virtual file system that may be helpful in unit tests to mock the real file system. It can be used with

null 1.4k Dec 23, 2022
A simple way to mock a client

Mocked Client A simple way to mock a client Install Via Composer $ composer require doppiogancio/mocked-client guzzlehttp/guzzle php-http/discovery No

Fabrizio Gargiulo 17 Oct 5, 2022
Very simple mock HTTP Server for testing Restful API, running via Docker.

httpdock Very simple mock HTTP Server for testing Restful API, running via Docker. Start Server Starting this server via command: docker run -ti -d -p

Vo Duy Tuan 4 Dec 24, 2021
Mock implementation of the Translation package, for testing with PHPUnit

PoP Translation - Mock Mock implementation of the Translation package, for testing with PHPUnit Install Via Composer composer require getpop/translati

PoP 1 Jan 13, 2022
Mockery - Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library

Mockery Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its c

Mockery 10.3k Jan 1, 2023
A PHP library for mocking date and time in tests

ClockMock Slope s.r.l. ClockMock provides a way for mocking the current timestamp used by PHP for \DateTime(Immutable) objects and date/time related f

Slope 44 Dec 7, 2022
Some shorthand functions for skipping and focusing tests.

Pest Plugin: Shorthands This repository contains the Pest Plugin Shorthands. If you want to start testing your application with Pest, visit the main P

Thomas Le Duc 10 Jun 24, 2022
Additional PHPUnit assertions and helper functions

Jasny PHPUnit extension Additional functionality for PHPUnit. Callback mock - assert that callback is called with correct arguments. Safe mocks - disa

Arnold Daniels 2 Jul 24, 2022
A Pest plugin to control the flow of time

This Pest plugin offers a function testTime that allows you to freeze and manipulate the current time in your tests.

Spatie 34 Nov 16, 2022
To run time/IO related unit tests (e.g., sleep function calls, database queries, API calls, etc) faster using Swoole.

To run time/IO related unit tests (e.g., sleep function calls, database queries, API calls, etc) faster using Swoole.

Demin Yin 11 Sep 9, 2022
PHP unit testing framework with built in mocks and stubs. Runs in the browser, or via the command line.

Enhance PHP A unit testing framework with mocks and stubs. Built for PHP, in PHP! Quick Start: Just add EnhanceTestFramework.php and you are ready to

Enhance PHP 67 Sep 12, 2022
Extension to use built-in PHP server on Behat tests

Extension to use built-in PHP server on Behat tests Instalation composer require libresign/behat-builtin-extension Configuration Add the extension to

LibreSign 2 Feb 21, 2022
PHP client for Selenium/WebDriver protocol. Previously facebook/php-webdriver

Php-webdriver library is PHP language binding for Selenium WebDriver, which allows you to control web browsers from PHP.

php-webdriver 4.7k Jan 3, 2023
The modern, simple and intuitive PHP unit testing framework.

atoum PHP version atoum version 5.3 -> 5.6 1.x -> 3.x 7.2 -> 8.x 4.x (current) A simple, modern and intuitive unit testing framework for PHP! Just lik

atoum 1.4k Nov 29, 2022
Full-stack testing PHP framework

Codeception Modern PHP Testing for everyone Codeception is a modern full-stack testing framework for PHP. Inspired by BDD, it provides an absolutely n

Codeception Testing Framework 4.6k Jan 7, 2023
Faker is a PHP library that generates fake data for you

Faker Faker is a PHP library that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in

FakerPHP 2.7k Dec 27, 2022
AST based PHP Mutation Testing Framework

Infection - Mutation Testing framework Please read documentation here: infection.github.io Twitter: @infection_php Discord: https://discord.gg/ZUmyHTJ

Infection - Mutation Testing Framework for PHP 1.8k Jan 2, 2023