PHPBuilder - Advanced Mocking with PHPUnit



RSS Twitter
Articles

Advanced Mocking with PHPUnit

by: John Barlow
|
December 17, 2014

In my previous article, Test Driven Development with PHPUnit, I detailed how to start using PHPUnit to do Test Driven Development. The examples stopped short of showing the real power of PHPUnit when it comes to Mocking objects, so that is what we will take a deeper look at in this piece.

 

Mock Objects, What Are They?

As stated previously, mock objects give the user the ultimate power over their testing environment. They let you stub out literally anything in your code to focus on one particular unit of code (and thusly test the snot out of it).

 

The most basic mock object that PHPUnit gives is one where you can change the outputs of functions, or put hooks on function calls to verify that it was called a particular number of times or with particular arguments (and these can be controlled with dataproviders).

 

One way of constructing the mock object is to replace functions with stubs that just return NULL. This is useful for utility functions that call an external resource such as an API or database. This is useful for when you don't care what comes back, but the code you are testing calls a function that needs to be accounted for. To set up the NULL stubs, create your mock object with the following structure (including the setMethods call:

 

$fooclass = $this->getMockBuilder('FooClass') 
                 ->disableOriginalConstructor()
                 ->setMethods(array(<comma separated method list>))
                 ->getMock();

 

This allows you to specify the methods that will be completely replaced with NULL when called. Also, if you are utilizing the magic methods (such as get and set) this is the only way to stub or hook into dynamic methods.

 

A Test Case

In our previous example, we were testing our FooClass that has methods foo and bar. We had already written a test that covers foo, but now we need to do bar (which calls foo). We've already tested foo, so we don't need to test it again. Mocks to the rescue!

 

Determining exactly how we stub foo for the bar test depends on how foo uses bar. In the most simple example, let's say that foo is just called at some point in bar to do a job, but the functionality of bar doesn't necessarily need to know about foo. This covers the NULL return we talked about earlier.

 

Bar's definition:

 

public function bar()
{
    $this->foo();
    <do other stuff>
    return true;
}

 

Since we have to account for foo being called (but we don't necessarily care what happens when it is called), the test for this would look like:

 

public function testBar()
{
    $fooclass = $this->getMockBuilder('FooClass') 
                               ->disableOriginalConstructor()
                               ->setMethods(array("foo"))
                               ->getMock();
    
    $returnVal = $fooclass->bar();
    $this->assertEquals(true, $returnVal);
}

 

The mock call uses the setMethods method to specify that we want foo to be completely replaced with a function that returns NULL. This allows us to call bar, which calls foo, but not actually exercise the code in foo.

 

Now, if bar depended more on foo, the test would be slightly different. For example, let's take this definition of bar:

 

public function bar()
{
    $grade = $this->foo();
    $sReturn = "";

    if($grade >= 90){
        $sReturn = "Excellent!";
    } else if($grade >= 60 && $grade < 90){
        $sReturn = "Adequate...";
    } else {
        $sReturn = "Why U No Study?";
    }

    return $sReturn;
}

 

When bar is called, it returns specific messages based on what foo returns. Again, given that we have already tested foo and know it works, we want to mock the responses for foo to test the three cases of bar. The test setup would look like this (note this this is a perfect use case for dataproviders as well.

 

/**
 * @dataProvider barTestProvider
 */
public function testBar($value, $returnValue)
{
    $fooclass = $this->getMockBuilder('FooClass') 
                               ->disableOriginalConstructor()
                               ->setMethods(array("foo"))
                               ->getMock();

    $fooclass->expects($this->any())
                   ->method("foo")
                   ->will($this->returnValue($value);
    
    $sReturn = $fooclass->bar();
    $this->assertEquals($returnValue, $sReturn);
}

public function barTestProvider()
{
    return array(
        array(91, "Excellent!"),
        array(75, "Adequate..."),
        array(42, "Why U No Study?")
    );
}

 

In the test setup above, the dataprovider sets up foo to return three values in order: 91, 75, and 42. Each response is also coupled with the expected return value from bar, so that for each test case, we have an input, and an expected output (all while mocking foo and controlling what IT returns).

 

Time and time again, I'll run across something I'm working on and think "This is too hard to test." I've found that if I am saying this to myself, than the function I'm working with is too complex and needs to be broken up into smaller testable units. Future me is happy, and present me can finish the tests and tickets in a timely manner.

 

In the next article, we will cover how to use mock objects across classes (it's really simple if you've been following along so far) and some other useful tricks for setting up an entire test class with unique values to be used in each test in the class.

 

 

Comment and Contribute

Your comment has been submitted and is pending approval.

Author:
John Barlow

Comment:



Comment:

(Maximum characters: 1200). You have characters left.