Announcing Function Mocker LE

Announcing Function Mocker Light Edition.

Another Function Mocker?

The function-mocker library was born to cover my need for a user-land, non runkit based, function replacement solution.
In a test one might need to replace a function that is already defined because that function is used by objects or functions one is testing and that will always be loaded.
In its most basic use function-mocker, thanks to the power provided by the Patchwork library, makes possible mocking, stubbing and spying functions that have been already defined; this proves extremely useful when working with WordPress where there is no selective loading of functionalities, at least not a reliable and clear one, in place.
In the context of a WordPress integration (or “WordPress unit”) test powered by wp-browser by the time the test method runs the get_option function is already defined by WordPress and Function Mocker provides a way to replace it in the context of a single test method only and for the purpose of setting an expectation on it:

public function test_logging_to_option(){
    $save_to = 'logs';
    $update_option = tad\FunctionMocker\FunctionMocker::replace('update_option');

    $object = new MyLogger($save_to);
    $object->save();

    $update_option->wasCalledWithOnce([$save_to]);
}

Inheriting the power of the Patchwork framework Function Mocker will be able to redefine internal PHP functions too if so configured.
The third functionality Function Mocker provides is that it allows replacing functions that are yet defined as well providing a convenient way to run the same code from the test method above without actually loading WordPress. This last functionality I’ve seen more and more used by me and other developers to write real unit tests for classes that will, but, rely on WordPress methods; while one could use an adapter to do the same wrapping utility functions in adapters without providing any tangible value complicates things.

Enter Function Mocker LE

What the “Light Edition” of Function Mocker does is providing just the last functionality: mocking, stubbing and spying all can be achieved using PhpUnit mock engine, Prophecy (my favourite), or any other method of asserting things.
The advantage of using Function Mocker LE over the standard version is its “light” nature: no costly PHP stream manipulation and less functionalities in exchange for speed. Straight from the documentation here are some examples using WordPress functions:

<?php

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Assert;
use function tad\FunctionMockerLe\define;
use function tad\FunctionMockerLe\defineWithMap;
use function tad\FunctionMockerLe\defineWithValueMap;
use function tad\FunctionMockerLe\defineAll;

class MyOrmTest extends TestCase {

    public function test_query_parameters_are_preserved(){
        // define a non existing function
        define('get_posts', function(array $args){
            Assert::assertEquals(['post_type' => 'book','paginated' => 2, 'posts_per_page' => 6], $args);

            return [];
        });

        $orm = new MyOrm('book');

        $orm->query()
            ->page(2)
            ->perPage(6)
            ->fetch();
    }

    public function test_users_cannot_fetch_posts_they_cannot_read(){
        // define a number of functions using a <function> => <callback> map
        defineWithMap([
            'is_user_logged_in' =>  function(){
                return true;
            },
            'get_posts' => function(){
                return[
                    ['ID' => 1, 'post_title' => 'One'],
                    ['ID' => 2, 'post_title' => 'Two'],
                    ['ID' => 3, 'post_title' => 'Three'],
                    ['ID' => 4, 'post_title' => 'Four']
                ];
            },
        'current_user_can' => function($cap, $id){
                // a post ID to 'can' map
                $map  = [
                    1 => true,
                    2 => true,
                    3 => false,
                    4 => true
                ];

                return $map($id);
                }
        ]);

        $orm = new MyOrm('book');

        $books = $orm->query()
            ->fetch();

        $expected = [
                ['ID' => 1, 'post_title' => 'One'],
                ['ID' => 2, 'post_title' => 'Two'],
                ['ID' => 4, 'post_title' => 'Four']
        ];

        $this->assertEquals($expected, $books);
    }

    public function test_sticky_posts_are_not_prepended_if_not_home_archive_or_tag(){
        // define a number of functions all with the same callback...
        defineAll(['is_home', 'is_archive', 'is_tag'], function(){
            return false;
        });
        define('get_posts', function(array $args){
            Assert::arrayHasKey('ignore_sticky_posts,', $args);
            Assert::assertEquals('1', $args['ignore_sticky_posts']);
        });

        $orm = new MyOrm('book');
        $orm->query()->fetch();

        // ...and then redefine them
        defineWithValueMap([
            'is_home' => true,
            'is_archive' => false,
            'is_tag' => false
        ]);

        // redefine as needed
        define('get_posts', function(array $args){
            Assert::arrayNotHasKey('ignore_sticky_posts,', $args);
        });

        $orm->query()->fetch();
    }
}

As function-mocker-le repository reads:

When function-mocker is overkill.

An example usage will follow soon.