Etsy sync WordPress plugin 05

Using Function Mocker to make my life easier on the WordPress Etsy plugin.

Function Mocker

I’ve updated function-mocker while using it in an array of environments and situations to be my go-to test tool.
My development happens for the most part in WordPress PHP code and while I can write good “WordPress way” code, testing it might be a pain with its heavy reliance on globals and side effects.
As small as the current Etsy Little Helper project might be it still poses some challenges in testing terms.
As an OOP lover I grind my teeth anytime I have to use globally defined functions and variables but there is a tradeoff to make when writing code and WordPress has proven its worth more than once.

The mind shift

One “should not mock what does not control” which, in a WordPress environment, means all the functions, classes and globals. A measure of control can be gained using adapters but that would be an additional layer to develop and maintain that’s not adapting anything and is creating a testing construct.
Rounding back this means I will mock WordPress defined functions and globals and deal with it.

Testing an activation hook

The main plugin class, ELH_Main, has the responsibility to kickstart the plugin hooking components around; it is using the ELH_DI as a service locator (the dependency injection container is a class dependency) and I’m breaking a rule knowing it (it’s an anti pattern).
Nonetheless I want to test the class and know that it is hooking where and how intended.

class ELH_Main {

    const SYNC_HOOK = 'elh_sync';

    /**
     * @var ELH_DI
     */
    protected $di_container;


    public static function instance( ELH_DI $di_container ) {
        $instance = new self;

        $instance->set_di_container( $di_container );

        return $instance;
    }

    public function hook_base() {
        register_activation_hook( ELH_ROOT, array( $this, 'activate' ) );
        register_deactivation_hook( ELH_ROOT, array( $this, 'deactivate' ) );
        add_action( 'plugins_loaded', array( $this, 'load_text_domain' ) );

        return $this;
    }

    public function activate() {
        wp_clear_scheduled_hook( self::SYNC_HOOK );
        $start = $this->di_container->get_var( 'sync_start_time' );
        $interval = $this->di_container->get_var( 'sync_interval' );
        wp_schedule_event( $start, $interval, self::SYNC_HOOK );
    }

    public function deactivate() {
        wp_clear_scheduled_hook( self::SYNC_HOOK );
    }

    public function load_text_domain() {
        load_plugin_textdomain( 'elh', false, dirname( plugin_basename( ELH_ROOT ) ) . '/languages' );
    }

    public function set_di_container( ELH_DI $di_container ) {
        $this->di_container = $di_container;
    }

    public function hook_synchronizer() {
        $synchronizer = $this->di_container->make( 'synchronizer' );

        add_action( self::SYNC_HOOK, array( $synchronizer, 'sync' ) );

        return $this;
    }

}

Not much fantasy here beside the use of the dependency injection container. The problem should be testing it because verifying expectations and calls on functions would either not be possible or require a lot of stubbing; I’ve used function-mocker in the tests below to get around the problem and cover all the bases.

use tad\FunctionMocker\FunctionMocker as Test;

class ELH_MainTest extends PHPUnit_Framework_TestCase {

    public function setUp() {
        Test::setUp();
    }

    public function tearDown() {
        Test::tearDown();
    }

    /**
     * @test
     * it should hook activation, deactivation and text domain
     */
    public function it_should_hook_activation_deactivation_and_text_domain() {
        define( 'ELH_ROOT', 'foo' );
        $register_activation_hook = Test::replace( 'register_activation_hook' );
        $register_deactivation_hook = Test::replace( 'register_deactivation_hook' );
        $add_action = Test::replace( 'add_action' );

        $sut = new ELH_Main();

        $sut->hook_base();

        $register_activation_hook->wasCalledWithOnce( [ 'foo', [ $sut, 'activate' ] ] );
        $register_deactivation_hook->wasCalledWithOnce( [ 'foo', [ $sut, 'deactivate' ] ] );
        $add_action->wasCalledWithOnce( [ 'plugins_loaded', [ $sut, 'load_text_domain' ] ] );
    }

In the test above I’m replacing three WordPress functions and checking those have been called with the right arguments.

    /**
     * @test
     * it should hook_base the synchronizer to the sync hook_base on hook_base method
     */
    public function it_should_hook_the_synchronizer_to_the_sync_hook_on_hook_method() {
        $sync = Test::replace( 'ELH_Synchronizer' );
        $dic = Test::replace( 'ELH_DI' )->method( 'make', $sync )->get();
        $add_action = Test::replace( 'add_action' );

        $sut = new ELH_Main();
        $sut->set_di_container( $dic );

        $sut->hook_synchronizer();

        $add_action->wasCalledWithOnce( [ ELH_Main::SYNC_HOOK, [ $sync, 'sync' ] ] );
    }

In this test I’m replacing the dependency injection container and an instance of the ELH_Synchronizer class and checking a call to a WordPress function, add_action, with matching arguments.

    /**
     * @test
     * it should schedule the sync on activation
     */
    public function it_should_schedule_the_sync_on_activation() {
        $wp_clear_scheduled_hook = Test::replace( 'wp_clear_scheduled_hook' );
        $wp_schedule_event = Test::replace( 'wp_schedule_event' );
        $time = time();
        $dic = Test::replace( 'ELH_DI' )->method( 'get_var', function ( $alias ) use ( $time ) {
            $map = [
                'sync_start_time' => $time,
                'sync_interval'   => 'hourly'
            ];

            return $map[ $alias ];
        } )->get();

        $sut = new ELH_Main();
        $sut->set_di_container( $dic );

        $sut->activate();

        $wp_clear_scheduled_hook->wasCalledWithOnce( [ ELH_Main::SYNC_HOOK ] );
        $wp_schedule_event->wasCalledWithOnce( [ $time, 'hourly', ELH_Main::SYNC_HOOK ] );
    }

    /**
     * @test
     * it should clear sync schedule on deactivation
     */
    public function it_should_clear_sync_schedule_on_deactivation() {
        $wp_clear_scheduled_hook = Test::replace( 'wp_clear_scheduled_hook' );

        $sut = new ELH_Main();

        $sut->deactivate();

        $wp_clear_scheduled_hook->wasCalledWithOnce( [ ELH_Main::SYNC_HOOK ] );
    }
} 

And I’m replacing functions again and using callback powered return values to verify for proper hooking calls.

Main class test results

Next

I will move down the line of classes, now empty shells, to test any class that might contain any logic.

I appreciate your input