0

I would like to have a PHPUnit Mock which executes a method like normal, but then modifies the return value in some way before the function returns.

What I have

I have a set of derived classes, similar to below:

abstract class Base
{
    abstract protected function getUrl();
    public function callUrl() {
        $url = $this->getUrl();
        // some code to call the URL here
    }   
}

class Foo extends Base
{
    protected function getUrl() {
        return "http://www.example.com/Foo";
    }   
}

class Bar extends Base
{
    protected function getUrl() {
        return "http://www.example.com/Bar";
    }   
}

Please note the classes I have are much more complex, and some of the items I have to test have side-effects (such as writing to a database, etc).

The naive, duplicate code approach

If I only had a single derived class (eg; Foo), then I could do the following:

class FooMock extends Foo
{   
    protected function getUrl() {
        return parent::getUrl() . "?sandbox";
    }   
}   

class theTest extends PHPUnit_Framework_TestCase
{   
    public function testIt() {
        $mock = new FooMock();
        // assert something
    }
}

Unfortunately, this means I would need a specific "Mock" class for each derived class I want to test, all of which perform exactly the same function.

The preferred approach

Instead, I would like to be able to do something like the following:

function callback ($returnValue) {
    return $returnValue . "?sandbox";
}   

class theTest extends PHPUnit_Framework_TestCase
{   
    private $mock;

    public function testFoo() {
        $this->mock = $this->getMockBuilder('Foo')->getMock();
        $this->setupMock();
        // assert something
    }

    public function testBar() {
        $this->mock = $this->getMockBuilder('Bar')->getMock();
        $this->setupMock();
        // assert something
    }

    public function setupMock() {
        $this->mock->expects($this->any())
            ->method('getUrl')
            ->will($this->postProcessReturnValue('callback'));
    }   
}

Is this at all possible with PHPUnit?


Update: It was suggested I have an instance of the original class, and an instance of the mock class. Use the original class to get the original return value and modify that. This modified value is then used as the return for the Mock. This is not a feasible way to go about things as the classes are more complex (they have side effects such as writing to the DB).

An example where this would not work;

class Foo extends Base
{
    $id = 0;
    public function saveToDB() {
        $this->id = saveToDBAndReturnId();
    }
    protected function getUrl() {
        if ($this->id > 0) {
            return "http://www.example.com/".$this->id;
        }
        throw new Exception("No ID");
    }
}
$foo = new Foo();
$foo->saveToDB();
$url = $foo->getUrl();

Obviously the returned URL would be different between multiple calls. I could always mock saveToDB, but that's starting to feel dirty when all I want to do is post-process the result of getUrl.

1 Answer 1

5

PHPUnit allows you to define a stub method that will use a callback to determine what to return.

$this->mock->expects($this->any())
     ->method('getUrl')
     ->will($this->returnCallback('callback'));

You can define your callback to call the original class method and modify the return value.

Of course, using mock objects in this way more or less defeats the purpose of having them be "mock" objects, since the mock objects will now rely on the underlying implementation.

Sign up to request clarification or add additional context in comments.

8 Comments

I had read about returnCallback, but how would I have it call the original class's method? The only object I have is the mock, and that would call the callback again...
Re; your second point. I agree, but in this case I consider it bending the rules rather than breaking them (a necessary evil perhaps).
You'll have to have your code instantiate a object of the original class (you could also store it as a property of the test class), and then your callback could just call the original method on the original object.
That feels even dirtier than just creating class FooMock, class BarMock, etc, as it would require altering the application code to accommodate the test.
OK but HOW do I call the original method? Does PHPUnit provide any facility for this, or am I left having to do the dreaded reflection five-liner?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.