7

I'm wondering if there's a fairly concise way of mocking objects which support chaining of methods... so for example, a database query object might have a method call that looks like this:

$result = $database->select('my_table')->where(array('my_field'=>'a_value'))->limit(1)->execute();

The problem comes if I have to mock two different select queries so that they return different results. Any ideas?

This is specifically about PHPUnit, but experiences from other unit testing frameworks will help.

Nathan MacInnes
  • 11,033
  • 4
  • 35
  • 50
  • can you please clarify whether you want to mock the object, e.g. find out if it's invoked or stub the return value of a method call. Or in other words, please explain what you are trying to use the test double for. – Gordon Nov 20 '10 at 13:24
  • @Gordon Sorry, I tend to use the terms mock and stub interchangeably. Bad habit. In my whole test suite, I'd like to do both. So in this example, I might stub the return value of a select query, but mock an insert. If you have suggestions for one or the other, that'd help. Thanks. – Nathan MacInnes Nov 20 '10 at 13:33
  • sorry, I still dont fully understand what you are trying to do. Could you show the testcase please? – Gordon Nov 20 '10 at 14:01
  • @Gordon It's a little too complex to explain here (hence the example), but the object I'm testing uses another object with a similar interface to the query example. I want to mock/stub the second object so that my tests for the first doesn't rely on in. – Nathan MacInnes Nov 20 '10 at 14:04

3 Answers3

14

I am not sure this is what you are looking for, so please leave a comment:

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testChainingStub()
    {
        // Creating the stub with the methods to be called
        $stub = $this->getMock('Zend_Db_Select', array(
            'select', 'where', 'limit', 'execute'
        ), array(), '', FALSE);

        // telling the stub to return a certain result on execute
        $stub->expects($this->any())
             ->method('execute')
             ->will($this->returnValue('expected result'));

        // telling the stub to return itself on any other calls
        $stub->expects($this->any())
             ->method($this->anything())
             ->will($this->returnValue($stub));

        // testing that we can chain the stub
        $this->assertSame(
            'expected result',
            $stub->select('my_table')
                 ->where(array('my_field'=>'a_value'))
                 ->limit(1)
                 ->execute()
        );
    }
}

You can combine this with expectations:

class StubTest extends PHPUnit_Framework_TestCase
{
    public function testChainingStub()
    {
        // Creating the stub with the methods to be called
        $stub = $this->getMock('Zend_Db_Select', array(
            'select', 'where', 'limit', 'execute'
        ), array(), '', FALSE);

        // overwriting stub to return something when execute is called
        $stub->expects($this->exactly(1))
             ->method('execute')
             ->will($this->returnValue('expected result'));

        $stub->expects($this->exactly(1))
             ->method('limit')
             ->with($this->equalTo(1))
             ->will($this->returnValue($stub));

        $stub->expects($this->exactly(1))
             ->method('where')
             ->with($this->equalTo(array('my_field'=>'a_value')))
             ->will($this->returnValue($stub));

        $stub->expects($this->exactly(1))
             ->method('select')
             ->with($this->equalTo('my_table'))
             ->will($this->returnValue($stub));

        // testing that we can chain the stub
        $this->assertSame(
            'expected result',
            $stub->select('my_table')
                 ->where(array('my_field'=>'a_value'))
                 ->limit(1)
                 ->execute()
        );
    }
}
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Looks like I'm gonna have to use this method, with a return callback which varies the result... either based on a counter or based on the backtrace, depending on which is most appropriate. It's not quite as concise as I'd hoped, but it'll work. Thanks all for contributions. – Nathan MacInnes Nov 20 '10 at 15:13
  • @Nathan that's the thing I dont get :) What are you trying to do that you need to look at the backtrace? Just have `execute` return whatever you expect the entire chain to return. – Gordon Nov 20 '10 at 15:18
  • But what if I make two different queries in a test case? That's where it gets difficult. – Nathan MacInnes Nov 22 '10 at 09:12
  • @Nathan why? just change the Stub to return a different result from execute then. – Gordon Nov 22 '10 at 09:34
  • 1
    @Nathan: You can use `$this->at($index)` to return different values from multiple calls to `execute()`. Unfortunately, `$index` here counts _all_ method invocations to the same mock such as `select()` and `where()` [all mocks? I forget], so it can be a real PITA to get them right. You might be better off writing a custom stub class. – David Harkness Feb 05 '11 at 08:39
  • I keep getting call to member function on a non-object when I try this. Most of the chained functions are also static functions, will that be a problem? – CMCDragonkai Nov 19 '13 at 18:06
  • @CMCDragonkai yes, see http://stackoverflow.com/questions/2357001/phpunit-mock-objects-and-static-methods – Gordon Nov 19 '13 at 19:53
1

I know its an old question, but it might help more to future googler's.

I was also having problems in finding a framework that will provide simple and easy syntax for mocking & stubbing method chainning. then I've decided to write a simple and easy-to-use mocking library.

Usage example:

 // Creating a new mock for SimpleClassForMocking
 $mock = ShortifyPunit::mock('SimpleClassForMocking');

  ShortifyPunit::when($mock)->first_method()
                            ->second_method(2,3)->returns(1);

  ShortifyPunit::when($mock)->first_method()
                            ->second_method(2,3,4)->returns(2);

  ShortifyPunit::when($mock)->first_method(1)
                            ->second_method(2,3,4)->returns(3);

  ShortifyPunit::when($mock)->first_method(1,2,3)
                            ->second_method(1,2)->third_method()->returns(4);

  $mock->first_method()->second_method(2,3); // returns 1
  $mock->first_method()->second_method(2,3,4); // returns 2
  $mock->first_method(1)->second_method(2,3,4); // returns 3
  $mock->first_method(1,2,3)->second_method(1,2)->third_method(); // return 4

GitHub:

https://github.com/danrevah/ShortifyPunit#stubbing-method-chanining

D_R
  • 4,894
  • 4
  • 45
  • 62
0

This might not be the answer you're looking for, but I wrote a mock object framework a couple of years back that will handle this sort of "depends on the input" assertion fine:

http://code.google.com/p/yaymock/

http://code.google.com/p/yaymock/wiki/Expectations

I wrote it for use in the unit tests backing Swift Mailer but it hasn't been widely adopted by any other projects (that I know of). The purpose was to provide better control and introspection of mock object invocations than that provided by PHPUnit and SimpleTest.

d11wtq
  • 34,788
  • 19
  • 120
  • 195
  • Thanks. I'll have a look at it, but I'm not sure I'm up for using yet another mock framework (just getting towards the end of migrating my existing tests from SimpleTest). – Nathan MacInnes Nov 20 '10 at 14:13
  • Aye, to be honest I'd also probably stick with something more widely used, especially if you're likely to have other devs working on the same code and don't want them to have to spend a day figuring out "yet another" mock object framework ;) I'm certainly not attached to it, but it does a nice job at what it was written to do so I thought I'd mention it anyway. – d11wtq Nov 20 '10 at 14:20