/monkey-patch

Standalone package of ci-phpunit-test's Monkey Patching.

Primary LanguagePHPMIT LicenseMIT

Monkey Patch

This package is a standalone package of ci-phpunit-test 's Monkey Patching.

This provides four monkey patchers.

  • ExitPatcher: Converts exit() to Exception
  • FunctionPatcher: Patches Functions
  • MethodPatcher: Patches Methods in User-defined Classes
  • ConstantPatcher: Changes Constant Values

Table of Contents

Requirements

  • PHP 7.3 or later

Installation

$ composer require --dev kenjis/monkey-patch

Usage

Note: The line number when an error occurs is probably different from the actual source code. Please check the cache file of the source that Monkey Patching creates.

Note: Using this package has a negative impact on speed of tests.

Configure

Convert exit() to Exception

This patcher converts exit() or die() statements to exceptions on the fly.

If you have a controller like below:

    public function index()
    {
        $this->output
            ->set_status_header(200)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode(['foo' => 'bar']))
            ->_display();
        exit();
    }

A test case could be like this:

    public function test_index()
    {
        try {
            $this->request('GET', 'welcome/index');
        } catch (ExitException $e) {
            $output = ob_get_clean();
        }
        
        $this->assertContains('{"foo":"bar"}', $output);
    }

Patch Functions

This patcher allows replacement of global functions that can't be mocked by PHPUnit.

But it has a few limitations. Some functions can't be replaced and it might cause errors.

So by default we can replace only a dozen pre-defined functions in FunctionPatcher.

    public function test_index()
    {
        MonkeyPatch::patchFunction('mt_rand', 100, 'Welcome::index');
        
        $output = $this->request('GET', 'welcome/index');
        
        $this->assertContains('100', $output);
    }

MonkeyPatch::patchFunction() replaces PHP native function mt_rand() in Welcome::index method, and it will return 100 in the test method.

Note: If you call MonkeyPatch::patchFunction() without 3rd argument, all the functions (located in include_paths and not in exclude_paths) called in the test method will be replaced. So, for example, a function in your library code might be replaced, and it results in an unexpected outcome.

Change Return Value

You could change return value of patched function using PHP closure:

        MonkeyPatch::patchFunction(
            'function_exists',
            function ($function) {
                if ($function === 'random_bytes') {
                    return true;
                } elseif ($function === 'openssl_random_pseudo_bytes') {
                    return false;
                } elseif ($function === 'mcrypt_create_iv') {
                    return false;
                } else {
                    return __GO_TO_ORIG__;
                }
            },
            Welcome::class
        );

Patch Other Functions

If you want to patch other functions, you can add them to functions_to_patch in MonkeyPatchManager::init().

But there are a few known limitations:

  • Patched functions which have parameters called by reference don't work.
  • You may see visibility errors if you pass non-public callbacks to patched functions. For example, you pass [$this, 'method'] to array_map() and the method() method in the class is not public.

Patch Methods in User-defined Classes

This patcher allows replacement of methods in user-defined classes.

    public function test_index()
    {
        MonkeyPatch::patchMethod(
            Category_model::class,
            ['get_category_list' => [(object) ['name' => 'Nothing']]]
        );
        
        $output = $this->request('GET', 'welcome/index');
        
        $this->assertContains('Nothing', $output);
    }

MonkeyPatch::patchMethod() replaces get_category_list() method in Category_model, and it will return [(object) ['name' => 'Nothing']] in the test method.

Patch Constants

This patcher allows replacement of constant value.

    public function test_index()
    {
        MonkeyPatch::patchConstant(
            'ENVIRONMENT', 
            'development', 
            Welcome::class . '::index'
        );

        $output = $this->request('GET', 'welcome/index');

        $this->assertContains('development', $output);
    }

MonkeyPatch::patchConstant() replaces the return value of the constant ENVIRONMENT in Welcome::index method.

There are a few known limitations:

  • Cannot patch constants that are used as default values in function arguments.
  • Cannot patch constants that are used as default values in constant declarations.
  • Cannot patch constants that are used as default values in property declarations.
  • Cannot patch constants that are used as default values in static variable declarations.

Class Reference

See ci-phpunit-test docs.

License

This package is licensed using the MIT License.

Please have a look at LICENSE.