A better mock object builder for PHPUnit

When working with mocks for unit tests, you're going to write a lot of code for the mocks. There are a lot of functions you're going to need to call every single time. To get around this you can use the following utility function[1]:

/**
 * Get Mock object
 *
 * @param string $class Class to mock with namespace
 * @param array|null $methods Methods to mock with key return => return value and key call with ether once, any, or a numeric value which has to be matched exactly
 * @param array|null Sets the mock constructor args or disables mock constructor when null is given
 *
 * @return \PHPUnit_Framework_MockObject_MockObject Mock object
 *
 * @codeCoverageIgnore
 */
public function getMockObject($class, $methods, $construct = null)
{
    /** @var \PHPUnit_Framework_MockObject_MockBuilder */
    $mockBuilder = $this->getMockBuilder($class)
        ->setMethods(array_keys($methods))
        ->disableOriginalConstructor();

    if($construct === null) {
        $mockBuilder->disableOriginalConstructor();
    } elseif(!empty($construct)) {
        $mockBuilder->setConstructorArgs($construct);
    }

    $mock = $mockBuilder->getMock();

    foreach($methods as $method => $definition) {

        if(is_null($definition)) {
            continue;
        }

        if(isset($definition['return_type'])) {
            switch($definition['return_type']) {
                case 'value_map':
                    $return = $this->returnValueMap($definition['return']);
                    break;
                case 'callback':
                    $return = $this->returnCallback($definition['return']);
                    break;
                case 'consecutive':
                    $return = call_user_func_array(array($this, 'onConsecutiveCalls'), $definition['return']);
                    break;
                case 'exception':
                    $return = $this->throwException($definition['return']);
                    break;
                default:
                    $return = $this->returnValue($definition['return']);
                    break;
            }
        } else {
            $return = $this->returnValue($definition['return']);
        }

        if(isset($definition['call'])) {
            switch($definition['call']) {
                case 'once':
                    $call = $this->once();
                    break;
                case 'any':
                    $call = $this->any();
                    break;
                case 'at':
                    // return should contain array with "at" values
                    break;
                default:
                    if(is_numeric($definition['call'])) {
                        $call = $this->exactly($definition['call']);
                    } else {
                        $call = $this->any();
                    }
                    break;
            }
        } else {
            $call = $this->any();
        }

        // Add at-matchers
        if(isset($definition['call']) && $definition['call'] == 'at') {
            foreach($definition['return'] as $at => $value) {
                $mock->expects($this->at($at))->method($method)->will($this->returnValue($value));
            }
        } else {
            $mock->expects($call)->method($method)->will($return);
        }
    }

    return $mock;
}

With this you be able to convert the following code

$mockBuilder = $this
    ->getMockBuilder('Liplex\Backend\Adapter')
    ->setMethods(['manageRights'])
    ->disableOriginalConstructor();

$mock = $mockBuilder->getMock();

$mock
    ->expects($this->any())
    ->method('manageRights')
    ->will($this->returnValue($response));

into the following:

$mock = $this->getMockObject(
    'Liplex\Backend\Adapter',
    [
        'manageRights' => [
            'return' => $response
        ]
    ]
);

  1. Code by Tschela Baumann ↩︎