Giter Site home page Giter Site logo

php-mock / php-mock-mockery Goto Github PK

View Code? Open in Web Editor NEW
37.0 37.0 4.0 109 KB

Mock non deterministic built-in PHP functions (e.g. time() or rand()) with Mockery.

License: Do What The F*ck You Want To Public License

PHP 100.00%
builtin-functions mockery php php-mock

php-mock-mockery's Introduction

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();

It's important to note that setNamespace() should target the namespace where the function is called, not the namespace where it's being mocked. For example:

<?php

namespace App\Code;

class Subject
{
  public function foo()
  {
    time();
  }
}

In a test mocking this call:

<?php

namespace Tests\Unit;

class SubjectTest
{
  public function myTest()
  {
    $builder = new MockBuilder();
    $builder->setNamespace('\\App\\Code'); // ... etc
  }
}

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

php-mock-mockery's People

Contributors

malkusch avatar michalbundyra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

php-mock-mockery's Issues

Support for multiple calls to the same function

There doesn't seem to be able simple way to set up expectations for multiple function calls.

I've ended up doing this as a workaround, but its ugly:

$mockFetchBody = PHPMockery::mock("Namespace", "imap_fetchbody");
$mockFetchBody
            ->with('someparams')
            ->andReturn('response');
$mockFetchBody
            ->getMock()
                ->shouldReceive(\phpmock\integration\MockDelegateFunctionBuilder::METHOD)
            ->with('nextparams')
            ->andReturn('response2');

Mocking json_decode

Is it possible to use the mocked json_decode for unit testing SomeClass::someMethod?
Where in someMethod() contains json_decode?

Summarized code snippet:

test Method:
PHPMockery::mock(NAMESPACE, "json_decode")
->andReturn("test");

$classMock = new SomeClass();
$actual = $classMock->someMethod();
$this->assertEquals("test", $actual);

SomeClass::someMethod:
// codes here
$var = json_decode($str);
// codes here

Cannot install because only php-mock 1.x is supported

php-mock-mockery only supports php-mock 1.x, so after installing php-mock 2.0 the following errors occur in composer:

Problem 1
- php-mock/php-mock-integration 1.0.0 requires php-mock/php-mock ^1 -> satisfiable by php-mock/php-mock[1.0.0, 1.0.1] but these conflict with your requirements or minimum-stability.
- php-mock/php-mock-mockery 1.2.0 requires php-mock/php-mock-integration ^1 -> satisfiable by php-mock/php-mock-integration[1.0.0].
- Installation request for php-mock/php-mock-mockery ^1.2 -> satisfiable by php-mock/php-mock-mockery[1.2.0].

I suspect this is probably just an oversight in composer.json, or is there a specific reason why php-mock-mockery would not work with php-mock 2.0? I posted the issue here although the problematic package is actually php-mock-integration, but I suspect nobody installs that package specifically.

Support for mockery 0.9.x

This package has a requirement of mockery/mockery with version ^0.8. Can this be updated to use the latest version of mockery?

Mockery::on() pass-by-reference behavior not preserved

Code Sample

Some code has been omitted. Please let me know if you need clarification.

namespace edu\wisc\doit\lists\wisclist;

class WiscList_DB
{
    public function execute($sth, $select = true)
    {
        $r = oci_execute($sth);
        if ($r === false) {
            $e = oci_error($sth);
            throw new DbQueryException($e[static::MESSAGE]);
        }
        $output = array();
        if ($select === true) {
            oci_fetch_all($sth, $output);
        }
        return $output;
    }
}

class WiscList_DBTest extends \PHPUnit_Framework_TestCase
{
    public function testExecuteReturnsDataWhenSelectTrue()
    {
        PHPMockery::mock(__NAMESPACE__, 'oci_execute')->with(anything())->andReturn(true)->once();
        PHPMockery::mock(__NAMESPACE__, 'oci_fetch_all')->with(
            anything(),
            \Mockery::on(function (array &$output) {
                $output = array('ID' => array(1));
                return true;
            })
        )->once();
        $data = static::getWiscListDbWithMockConfig()->execute(null, true);
        $this->assertEquals(array('ID' => array(1)), $data);
    }

    private static function getWiscListDbWithMockConfig()
    {
        return new WiscList_DB(static::getMockConfig());
    }
}

PHPUnit Test Output

PHPUnit 4.8.13 by Sebastian Bergmann and contributors.

Runtime:    PHP 5.6.15 with Xdebug 2.3.3
Configuration:  /Users/ahoffmann/IdeaProjects/wisclist-admin/phpunit.xml

F

Time: 119 ms, Memory: 7.75Mb

There was 1 failure:

1) edu\wisc\doit\lists\wisclist\WiscList_DBTest::testExecuteReturnsDataWhenSelectTrue
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    'ID' => Array (...)
 )

/Users/ahoffmann/IdeaProjects/wisclist-admin/tests/WiscList_DBTest.php:168

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Explanation

The goal is to mock the oci_fetch_all method, which takes an array by reference and fills it with the data that was fetched. I am using Mockery::on() to preserve pass-by-reference behavior. I have successfully done this in the past with vanilla Mockery. However, when using php-mock's integration with Mockery, the behavior is not preserved.

Environment

  • PHPUnit 4.8
  • PHP 5.5
  • Mockery 0.9.4
  • php-mock 1.0.0
  • php-mock-mockery 1.0.0

Troubleshooting

  • Confirmed that "vanilla" Mockery::on() preserves pass-by-reference behavior
  • Ran a debugger to watch the arguments being passed. The $output variable is modified in Mockery::on(), but is reset back to the original argument sometime after the Mockery::on() method returns.
  • Tried PHP 5.6 and PHPUnit 5, both still failed.

how to mock php native ldap fuctions like lda_connect, ldap_get_entries, ldap_search and ldap_read

I have the below class to connect to ldap server to check whether the user belongs to a group or not. i want to write unit test for this class. how do i achieve this. how to mock the ldap php native functions. can some one please help me with a bit of sample code.


<?php

namespace ABC\Admin\Login;

use Doctrine\ORM\EntityManagerInterface;

/**
 * Class Authenticate AD Login
 * @package Adp\Admin\Login
 */
class LdapAuthentication
{

/**
 * @var string host
 */
private $ldapHost;

/**
 * @var string Admin
 */
private $ldapDomain;

/**
 * @var string DN
 */
private $baseDn;

/**
 * @var EntityManagerInterface
 */
private $entityManager;

public function validateUser($user, $password)
{

    $ad = ldap_connect("$this->ldapHost") or die('Could not connect to LDAP server.');
    ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ad, LDAP_OPT_REFERRALS, 0);
    if (!ldap_bind($ad, "{$user}@{$this->ldapDomain}", $password)) {
        return false;
    }
    $userDn = $this->getDN($ad, $user, $this->baseDn);

    return $this->checkGroupEx($ad, $userDn, $groups);
    ldap_unbind($ad);

}


/**
 * @param $ad
 * @param $samAccountName
 * @param $baseDn
 * @return string
 */
private function getDN($ad, $samAccountName, $baseDn)
{
    $attributes = array('dn');
    $result = ldap_search(
        $ad,
        $baseDn,
        "(samaccountname={$samAccountName})",
        $attributes
    );
    if ($result === false) {
        return '';
    }
    $entries = ldap_get_entries($ad, $result);
    if ($entries['count'] > 0) {
        return $entries[0]['dn'];
    }

    return '';
}

/**
 * @param $ad
 * @param $userDn
 * @param $groups
 * @param $roles
 * @return bool
 */
private function checkGroupEx($ad, $userDn, $groups)
{
    $attributes = array('cn','memberof', 'mail');
    $result = ldap_read($ad, $userDn, '(objectclass=*)', $attributes);
    if ($result === false) {
        return false;
    };
    $entries = ldap_get_entries($ad, $result);
    $response = array();
    $name = explode(',', $entries[0]['cn'][0]);
    $response['firstName'] = $name[0];
    $response['lastName'] = $name[1];
    $response['email'] = $entries[0]['mail'][0];

    if ($entries['count'] <= 0) {
        return false;
    };
    if (empty($entries[0]['memberof'])) {
        return false;
    }
    for ($i = 0; $i < $entries[0]['memberof']['count']; $i++) {
        $groupName = explode(',', $entries[0]['memberof'][$i]);
        $pos = array_search(strtolower(substr($groupName[0], 3)), $groups);
        if (!empty($pos)) {

            return $response;
        }
    }
    return false;
}

/**
 * @return string
 */
public function getBaseDn()
{
    return $this->baseDn;
}

/**
 * @param string $baseDn
 */
public function setBaseDn($baseDn)
{
    $this->baseDn = $baseDn;
}

/**
 * @return string
 */
public function getLdapDomain()
{
    return $this->ldapDomain;
}

/**
 * @param string $ldapDomain
 */
public function setLdapDomain($ldapDomain)
{
    $this->ldapDomain = $ldapDomain;
}

/**
 * @return string
 */
public function getLdapHost()
{
    return $this->ldapHost;
}

/**
 * @param string $ldapHost
 */
public function setLdapHost($ldapHost)
{
    $this->ldapHost = $ldapHost;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.