Thursday, November 8th, 2012

Shmock: Tolerable Mocking in PHPUnit

By

This is the (short) story of how we built Shmock, a typesafe shorthand mocking library for PHPUnit that we now use extensively at Box.

“Ugh.” That’s often the sentiment I have when I see an excerpt of PHPUnit’s built-in mocking framework in action. Anyone who’s seen it used knows what I’m talking about. Consider:

<?php

class User_Test
{
    public function test_users_can_receive_email()
    {
        // set up our mock object
        $email_service = $this->getMock('Email_Service', array(), array(), '', false);
        $email_service->expects($this->once())
             ->method('send')
             ->with($this->equalTo('user@gmail.com', $this->stringContains('the message'))
             ->will($this->returnValue(true));

        $user = new User($email_service);
        $user->set_email('user@gmail.com');
        $user->send_email('the message');
    }
}

When I first joined Box, I had to convince people that mocking out important side effects (note: not implementation details) of your code was an important aspect of testing. And then we ran into PHPUnit’s mocking syntax. “Ugh”.

Finding this incredibly annoying, we decided to look at alternatives. We briefly considered Etsy’s Mockery and Phake. Both of these have advantages over the built-in syntax but neither really did it for us. Our requirements were

  • Un-invasive: It should take the smallest amount of work possible to set up
  • Type Safety: We didn’t want to sacrifice type safety when mocking in order to get an easier syntax.
  • Record/Replay/Verify: A few of us had a good experience in the Java world using EasyMock. We liked that it forced a test writer to divide their assembly step (record) from their act step (replay) and their assertion phase (verify). The AAA model helps with test readability significantly.So here’s what we came up with:
    <?php
    
    class User_Test
    {
        use \Shmock\Shmockers;
    
        public function test_users_can_receive_email()
        {
            // set up our mock object
            $email_service = $this->shmock('Email_Service', function($email_service)
            {
                $email_service->disable_original_constructor();
                $email_service->send('user@gmail.com', $this->stringContains("a message"))
                    ->return_true();
            });
    
            $user = new User($email_service);
            $user->set_email_address('user@gmail.com');
    
            $user->send_email("This is a message");
        }
    }
    

    The ‘shmock’ function takes a class name and a closure that is used to configure expectations. We take advantage of PHP’s anonymous function capability and use it to provide a “record” phase. The $email_service returned is a vanilla PHPUnit mock object which will be automatically verified by PHPUnit, just as before.

    There are a couple things I really like about this. Remember this line?

    $this->getMock('Email_Service', array(), array(), '', false);
    

    Any guesses on what it means? The purpose of this is actually to disable the original constructor of the Email_Service object. Obviously. Inside the shmock closure, this looks like:

    $email_service->disable_original_constructor();
    

    Although PHPUnit provides an alternate mock builder syntax, it’s very poorly documented. As a result, most developers first see the getMock method and leave ‘gems’ like the above getMock() call in our testing code base.

    We also have an opportunity to prevent common programming errors. When doing partial mocking (replacing some, but not all methods on an object) a common pitfall is to mock a private method. Because of how typesafe mocking must work in PHP, you cannot mock private methods. This is because a dynamic subclass of the target is evaluated at runtime – and since private methods cannot be overloaded, an attempt to mock a private method will not work.

    PHPUnit does not provide any warning of this. Nor does it warn you when you’ve mocked a method that doesn’t exist in a class. If duck typing is your thing, that’s cool, but PHP’s schizophrenic approach to type safety makes me want less flexibility, not more. So shmock will give you better messaging if you try to mock a method that ain’t gonna work.

     $ phpunit test
    PHPUnit 3.6.11 by Sebastian Bergmann.
    
    ....F.
    
    Time: 1 second, Memory: 3.00Mb
    
    There was 1 failure:
    
    1) Shmock\Shmock_Test::test_mocking_a_private_method_throws_exception
    Attempted to expect #you_cant_mock_this, which is not defined or is a static method in the
    class Shmock_Foo.
    If you wish to disable this check, call $shmock-&gt;disable_strict_method_checking()
    Failed asserting that false is true.
    

    If you’d like to use Shmock on your project, have a look at our documentation on the Github repo. We’re happy to be contributing this to the community – if you have questions / comments, feel free to add a comment here!