Mocking magic methods in tests

Getting around the limits of magic method mocking.

The problem

Using PHP magic methods is a practice frowned upon but it’s something that might sometimes be necessary.
A use case might be one where a client class is calling undefined methods on one of its dependencies like this:

class Page_Semaphore {

    protected $pages; 

    public function __construct( Pages $pages ) {
        $this->pages = $pages;
    }

    public function get_color_for_page( $page ) {
        if( $this->pages->{"{$page}_can_run"}( ) ) {
            return 'green';
        }

        return 'red';
    }
}

And its Pages dependency

class Pages {

    public function __call($name, $args) {
        $matches = array();

        if ( !preg_match( '/([a-zA-Z]+)_can_run/', $name, $matches ) ) {
            throw new BadMethodCallException( 'Only *_can_run is supported' );
        }

        $page = $matches[1];

        $page_post = get_page_by_path( $page );

        if ( empty($page_post) ) {
            return false;
        }

        $run_condition = get_post_meta( $page_post->ID, '_run_condition', true );

        return $run_condition === 'ok' ? true : false;
    }
}

While testing the Page_Semaphore class I could write two tests like these

class Page_Semaphore_Test extends PHPUnit_Framework_TestCase {
     /**
     * @test
     * it should return green if page is ok
     */
    public function it_should_return_green_if_page_is_ok() {
        $pages = $this->prophesize( 'Pages' );
        $pages->some_page_can_run()->willReturn( true );

        $semaphore = new Page_Semaphore( $pages->reveal() );

        $out = $semaphore->get_color_for_page( 'some_page' );

        $this->assertEquals( 'green', $out );
    }

    /**
     * @test
     * it should return red if page is other than ok
     */
    public function it_should_return_red_if_page_is_other_than_ok() {
        $pages = $this->prophesize( 'Pages' );
        $pages->some_page_can_run()->willReturn( false );

        $semaphore = new Page_Semaphore( $pages->reveal() );

        $out = $semaphore->get_color_for_page( 'some_page' );

        $this->assertEquals( 'red', $out );
    }
}

Running the tests will result in the mocking engine complaining about the undefined some_page_can_run method.
I’m using prophecy to mock objects here but the same would apply to PhpUnit.

A solution

The best solution would be not to rely on magic methods at all but ruled that out here is another one that’s based around inheritance.
While the $page parameter used by the Page_Semaphore::get_color_for_page might vary I can fix what page slugs will be used in my tests in a certain way.
The class below extends the Pages class providing a concrete empty implementation of the method.

class Test_Pages extends Pages {

    public function some_page_can_run () {

    }
}

and I will make the method behave the way I want mocking this class in my tests

class Page_Semaphore_Test extends PHPUnit_Framework_TestCase {
     /**
     * @test
     * it should return green if page is ok
     */
    public function it_should_return_green_if_page_is_ok() {
        $pages = $this->prophesize( 'Test_Pages' );
        $pages->some_page_can_run()->willReturn( true );

        $semaphore = new Page_Semaphore( $pages->reveal() );

        $out = $semaphore->get_color_for_page( 'some_page' );

        $this->assertEquals( 'green', $out );
    }

    /**
     * @test
     * it should return red if page is other than ok
     */
    public function it_should_return_red_if_page_is_other_than_ok() {
        $pages = $this->prophesize( 'Test_Pages' );
        $pages->some_page_can_run()->willReturn( false );

        $semaphore = new Page_Semaphore( $pages->reveal() );

        $out = $semaphore->get_color_for_page( 'some_page' );

        $this->assertEquals( 'red', $out );
    }
}

This will solve the problem while also not breaking the type hinting in place.