PHPUnit's returnValueMap not yielding expected results
Asked Answered
N

3

48

I'm trying to use PHPUnit's returnValueMap() to stub out the results of a read. It isn't yielding the expected results, but an equivalent returnCallback() does. I've made my test case available if you'd like to inspect it yourself.

returnValueMap()

$enterprise = $this->getMock('Enterprise', array('field'));
$enterprise->expects($this->any())
    ->method('field')
    ->will($this->returnValueMap(array(
        array('subscription_id', null),
        array('name', 'Monday Farms')
    )));
$enterprise->subscribe('basic');

Results:

Subscription ID: NULL
Name: NULL

returnCallback()

$enterprise = $this->getMock('Enterprise', array('field'));
$enterprise->expects($this->any())
    ->method('field')
    ->will($this->returnCallback(function ($arg) {
        $map = array(
            'subscription_id' => null,
            'name' => 'Monday Farms'
        );
        return $map[$arg];
    }));
$enterprise->subscribe('basic');

Results:

Subscription ID: NULL
Name: string(12) "Monday Farms"

Enterprise::subscribe

public function subscribe() {
    echo 'Subscription ID: ';
    var_dump($this->field('subscription_id'));
    echo 'Name: ';
    var_dump($this->field('name'));
}

Why doesn't returnValueMap() work as I expect it to? What exactly am I missing?

Ninetta answered 5/10, 2012 at 14:39 Comment(0)
N
128

I had the same problem and eventually found out that returnValueMap() has to map all parameters of your function, including optional ones, then the desired return value.

Example function from Zend Framework:

public function getParam($key, $default = null)
{
    $key = (string) $key;
    if (isset($this->_params[$key])) {
        return $this->_params[$key];
    }

    return $default;
}

Has to mapped like this:

$request->expects($this->any())
        ->method('getParam')
        ->will($this->returnValueMap(array(array($param, null, $value))));

Without the null in the middle, it won't work.

Neo answered 8/3, 2013 at 18:16 Comment(2)
You just saved me A LOT of headaches. Thanks. PS: Kind of a shame that a lot of the quirks of PHPUnit are not in its official documentation....Lorettalorette
Life saver! ther's also instances that the null needs to be false especially if $param (the key) is an integerGothic
M
9

I've been hunting down this same problem and eventually out of desperation xdebug-stepped through Framework/MockObject/Stub/ReturnValueMap.php and Framework/MockObject/InvocationMocker.php [in method InvocationMocker::invoke(PHPUnit_Framework_MockObject_Invocation $invocation) ], and I've observed the following points:

  1. Not only must the values in the map-array that you supply be the same as the expected parameters when the stubbed function is called, but they MUST BE OF THE SAME TYPE. This is because inside Framework/MockObject/Stub/ReturnValueMap.php in the ReturnValueMap::invoke() method, the comparison between the supplied parameters and the expected parameters is compared in line 75 as follows:

    if ($invocation->parameters === $map) {
    

    So, the anticipated result of

    $mock->myStubbedMethod( "1", "2" )
    

    using a map-array of

    array(
        array( 1, 1, 500 ),
        array( 1, 2, 700 )
    )
    

    will disappoint. Instead, the result will be a NULL.

  2. On a much more subtle point, you may have stubbed the method using two different mock scenarios (as I did - yes, silly me!)

    So, to elucidate, the first mock-stub could contain

    $mock->expects( $this->any() )
           ->method('myStubbedMethod')
           ->will( $this->returnValue(750) );
    

    and then later, in the same unit-test method, it could contain

    $arrMap = array(
        array( 1, 1, 500 ),
        array( 1, 2, 700 ),
        array( 2, 3, 1500 ),
    );
    $mock->expects( $this->any() )
            ->method('myStubbedMethod')
            ->will( $this->returnValueMap($arrMap) );
    

    When the stubbed-method is invoked, the map-array version will be implemented. This is obvious and self-evident, however while coding in the heat of the moment, and while separating different code behaviour in your mind as you develop, it's easily overlooked.

Morgan answered 27/5, 2015 at 12:38 Comment(0)
H
0

The Problem seems to lie either somewhere else in the code or might be an issue with an old PHPUnit version.

For me it works using this code:

cat EnterpriseTest.php

<?php

class EnterpriseTest extends PHPUnit_Framework_TestCase {

    public function testReturnValueMap() {
        $enterprise = $this->getMock('Enterprise', array('field'));
        $enterprise->expects($this->any())
            ->method('field')
            ->will($this->returnValueMap(array(
                array('subscription_id', null),
                array('name', 'Monday Farms')
            )))
        ;
        $enterprise->subscribe('basic');        
    }

}

class Enterprise {

    public function field($name) {
    }

    public function subscribe() {
        echo 'Subscription ID: ';
        var_dump($this->field('subscription_id'));
        echo 'Name: ';
        var_dump($this->field('name'));
    }

}

Output:

phpunit-dev EnterpriseTest.php 
PHPUnit 3.7.6 by Sebastian Bergmann.

.Subscription ID: NULL
Name: string(12) "Monday Farms"


Time: 0 seconds, Memory: 6.75Mb

OK (1 test, 1 assertion)

So while I can't tell why it doesn't work I can at least tell you that you are doing it right and that you've understood returnValueMap correctly :)

Horsetail answered 8/10, 2012 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.