An Introduction to Mock Object Testing

Tweet

If you are part of a development team, more often than not your code will also depend on code written by your teammates. But what if their code isn’t available at the moment — say, your teammate hasn’t finished writing it yet? Or, what if the code you need requires other external dependencies that are hard to set up? And what if you cannot test code because of other factors beyond your control? Will you just hang around, do nothing, and wait until your team is done or when everything is in place? Of course not!

In this this article, I’ll illustrate how to write code that works around this problem with dependencies. Some background on unit testing is ideal, and there is already a great introductory article here on SitePoint on unit testing with written by Michelle Saver. And although it’s not required for this article, do check out my other articles on automated database testing.

The Case for Mock Objects

As you may already have guessed, mock objects are a way out of the sticky situations I mentioned in the introduction. But what are mock objects? Mock objects are objects that stand in for the real implementation of an actual object.

Now why would you want a stand-in object instead of a real one?

Mock objects are used in unit-testing to mimic the behavior of real objects in test cases. By using them the functionality of the object you are implementing would be easier to test. Here are some situations where the use of a mock object is beneficial:

  1. The real implementation of one or more dependencies of an object has not been realized yet. Let’s say you’re tasked to do some processing on some data from a database. You would probably call on a method of some form of data access object or data repository, but what if the database has not been setup yet? What if the there was no data available (a situation I’ve encountered one too many times) or if the code that queries the database hasn’t been written yet? A mock data access object simulates the real data access object by returning some pre-defined values. This frees you from the burden of having to setup the database, look for data or write the code that queries the database.
  2. The real implementation of the dependencies of an object depends on factors that are hard to simulate. Suppose you would want to tabulate the likes and comments of a Facebook post by day. Where will you get your data? Your Facebook developer account is new. Heck, you still do not have friends! How will you simulate likes and comments? Mock objects offer a better approach than bothering your fellow developers to like or comment on some posts for you. And how will you simulate all these actions in a range of days if you wanted to show data by day? How about by month? What do you do if the unthinkable happens and Facebook is down at the moment? A mock object can pretend to be the Facebook library and return the data you need. You don’t have to go through the hassles I just mentioned just to get started working on your task.

Mock Objects in Action

Now that we know what mock objects are, let’s see some examples in action. We’ll implement our simple functionality previously mentioned, e.g tabulating likes and comments of a Facebook post.

We’ll start with the following unit test to define our expectations of how our object is going to be called and how what the return value will look like:

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    private $statusService;
    private $fbID = 1;

    public function setUp() {
        $this->statusService = new StatusService();
    }

    public function testGetAnalytics() {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));

        $this->assertEquals(array(
            "2012-01-01" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-02" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-03" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-04" => array(
                "comments" => 5,
                "likes"    => 3,
            ),
            "2012-01-05" => array(
                "comments" => 5,
                "likes"    => 3,
            )
        ), $analytics);
    }

}

If you run this test you will get a failure. This is expected, as we have not implemented anything yet!

Now let’s write up our implementation of the service. Of course the first step is to get the data from Facebook, so let us to do that first:

<?php
class StatuService
{
    private $facebook;

    public function getAnalytics($id, $from, $to) {
        $post = $this->facebook->get($id);
    }
}

This test too will fail because the Facebook object is null. You could plug in an actual implementation creating an actual instance with a Facebook app ID etc., but what for? We already all know what agony is involved to do that when it let’s you deviate from the task at hand. We can spare ourselves by injecting a mock one instead!

The way this is done using mock objects, at least in our case, is to create a class that has a get() method and returns mock values. This should fool our client into thinking that it is calling the real object implementation when in fact it was actually just mocked.

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    // test here
}

class MockFacebookLibrary
{
    public function get($id) {
        return array(
            // mock return from Facebook here
        );
    }
}

Now that we have a mock class, let’s instantiate an instance and then inject it into StatusService so that it can be used. But first, update StatusService with a setter for the Facebook library:

<?php
class StatusService
{
    // other lines of code

    public function setFacebook($facebook) {
        $this->facebook = facebook;
    }
}

Now inject the mock Facebook library:

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    public function testGetAnalytics {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));
        $analytics->setFacebook(new MockFacebook());

        // code truncated
    }
}

The test still fails, but at least we don’t get the error related to calling a method on a non-object anymore. More importantly though, you have just addressed the need to fulfill this dependency. You can now start programming the business logic you are tasked to do and pass the test.

PHPUnit 3.6.7 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 3.00Mb
There was 1 failure:
1) StatusServiceTest::testGetAnalytics null does not match expected type "array".
/home/jeune/mock-object-tutorial/ManualMockStatusServiceTest.php:43
FAILURES! Tests: 1, Assertions: 1, Failures: 1.

Taking it a Step Further: Using a Mocking Framework

While certainly you can live with using hand-crafted mocks when you are just starting out, later on, as I found out myself, you might have the need to use a real mocking framework as your needs become more complicated. In this article, I’ll show how to use the mocking framework that comes with PHPUnit.

In my experience, here are some benefits to using a mock framework over using manually written mocks:

  • You can afford to be lazy. I found this especially true if you are dealing with abstract classes with lots of abstract methods. You can afford to mock only certain methods of an abstract class or interface. If you were doing this manually, then you would have to implement all of them by hand. It saves you some typing and comes pre-packaged with type hinting as well; you only setup what you need and you don’t have to maintain a new class for each test case.
  • You can write cleaner code. Readability is the key here. Framework-based mocks make your tests easier to understand as your mocks are written in the test. You do not need scroll down or switch between files to view hand-written mocks written elsewhere. What if you needed to call your mock object more than once with different results? With framework-based mocks, the if-else boiler plate code required to do this is already well encapsulated. Hence, it is easier on the eyes.

Using PHPUnit’s Mocking Framework

Turning our attention to using PHPUnit’s Mocking Framework, the steps are actually pretty intuitive and, once you get the hang of it, it will be second nature. In this section we’ll be using PHPUnit’s Mocking Framework to create a mock object for our example situation.

Before we do that however comment out or remove the line that uses our hand-crafted mock object in our test. We need to fail first so we have something to pass. Later on, we will inject our new mock implementation.

<?php
class StatusServiceTest extends PHPUnit_Framework_TestCase
{
    public function testGetAnalytics {
        $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02"));
        //$analytics->setFacebook(new MockFacebook());

        // code truncated
    }
}

Verify that the test indeed fails when you run PHPUnit.

Now, think about how we mocked an object and the method manually that we want to invoke. What did we do?

  1. The first step is to identify what objects to mock. In our analytics example feature above, we mocked the Facebook library. We are doing the same thing as a first step.
  2. Now that we have defined what class to mock, we have to know what methods in the class we want to mock specifying parameters and return values if there are any. A basic template I use in most cases go somewhat like this:
  1. Specify the number of times a method will be called (Required).
  2. Specify the method name (Required).
  3. Specify the parameters that the method expects (Optional).
  4. Specify the return value (Optional)

Let’s apply the steps just mentioned to our example test.

<?php
    //code truncated
    public function testGetAnalytics()
    {
        $arr = array(// facebook return);

        $facebook = $this->getMock('facebook') // object to mock
            ->expects($this->once())    // number of times to be called
            ->method('get') // method name
            ->with(1) // parameters that are expected
            ->will($this->returnValue($arr)); // return value

            // code truncated
    }

After we have created our mock facebook object again, inject it back again into our service:

<?php
    //code truncated
    public function testGetAnalytics()
    {
        $arr = array(// facebook return);

        $facebook = $this->getMock('facebook') // object to mock
            ->expects($this->once())    // number of times to be called
            ->method('get') // method name
            ->with(1) // parameters that are expected
            ->will($this->returnValue($arr)); // return value

        $this->statusService->setFacebook($facebook);

        // code truncated
    }

Now, you should have a passing test again. Congratulations! You have hit the ground running with using mock objects for testing! Hopefully you should be able to program more efficiently and most of all break free from show stopper dependencies you run into in the future.

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://mathew-davies.co.uk Mathew

    I don’t think using a Mocking tool (like which PHPUnit provides) is all that great. The Mock object you wrote is more readable and can be used anywhere where PHPUnit can’t.

    • http://jeunito.me Jose Asuncion

      I have tried going down the route of writing hand crafted mocks before. What I found was
      1. I had to switch my attention between my test method and then scrolling down to view my handcrafted mock class.
      2. I tried to solve #1 by maintaining a mock class for a test in its own file.
      3. #2 presented problems like, what if I had wanted different returns from a method of a class for different test methods? At one point, I found myself maintaining one class and then having a long if-else that returns results depending on what the input was. This was very messy so I tried maintaining one mock class per test method but then I had the problem of maintaining too many mock classes.

      Using a mock framework eliminated these problems with me plus I don’t think you can get any more readable than a fluent interface. :)

    • http://socketo.me Chris Boden

      A great feature about mocking not listed here is you can mock an interface. Applying to this example, if the StatusService was polymorphic and requires a SocialInterface instead of a concrete Facebook class, PHPUnit could mock the interface and pass it to StatusService to test.

      • http://jeunito.me Jose Asuncion

        Oh I think I missed that one but I did mention that you could mock abstract classes. Thanks for the addition Chris :)

  • Julien

    Hey,
    Shouldn’t be the expect method called on $facebook rather than on $this->statusService ?

    • http://jeunito.me Jose Asuncion

      Hi Julien, thanks for the heads up on the typo. Will get the editor to change it.

  • Nick K

    Nice article.
    Another typo though: you’re passing a MockFacebook object as the setFacebook argument instead of the MockFacebookLibrary object ;)