Page 1 of 2

Mocking an object - getting a strange error

Posted: Sun Oct 14, 2007 12:44 pm
by Luke
I was getting a really strange error while trying to unit test the icalendar library I'm working on. So I wrote a less complex example so that I could eliminate as many variables as I could. The situation is basically like this:

Code: Select all

<?php
abstract class Item 
{
}

abstract class Book extends Item
{
}

class MobyDick extends Book
{
}

set_include_path(
    'C:\\htdocs\\attachable\\lib' . PATH_SEPARATOR .
    'C:\\phplib' . PATH_SEPARATOR .
    'C:\\htdocs\\library' . PATH_SEPARATOR .
    // add simpletest directory here
    get_include_path()
);

require_once 'simpletest/unit_tester.php';
require_once 'simpletest/reporter.php';
require_once 'simpletest/mock_objects.php';

Mock::generate('MobyDick');

$book = new MockMobyDick;
if ($book instanceof Item) echo 'w00t!';
Before I could even get to the point of diagnosing my first error, I got this error:
Fatal error: Call to a member function tell() on a non-object in C:\phplib\simpletest\mock_objects.php on line 413
What's causing this? I tried digging around in the simpletest code, but I failed to find anything helpful. :(

Posted: Sun Oct 14, 2007 6:29 pm
by Jenk
I had this before, I'm 90% sure it was this anyway. The resolution was that Mock Objects can only be instantiated within a testcase method; like so:

Code: Select all

class MyTest extends TestCase {
  public function testSomething () {
    $mock = new MockMyObject($this);
  }
}

Posted: Sun Oct 14, 2007 10:04 pm
by Luke
Thanks, that was exactly what it was.

Now on to the original problem. Shouldn't this pass?

Code: Select all

<?php
abstract class Item 
{
}

abstract class Book extends Item
{
}

class MobyDick extends Book
{
}

set_include_path(
    'C:\\htdocs\\attachable\\lib' . PATH_SEPARATOR .
    'C:\\phplib' . PATH_SEPARATOR .
    'C:\\htdocs\\library' . PATH_SEPARATOR .
    // add simpletest directory here
    get_include_path()
);

require_once 'simpletest/unit_tester.php';
require_once 'simpletest/reporter.php';
require_once 'simpletest/mock_objects.php';

Mock::generate('MobyDick');

class BookTest extends UnitTestCase
{
    public function testMockBook()
    {
        $book = new MockMobyDick;
        $this->assertTrue($book instanceof Item);
    }
}

$test = new BookTest('Tests');
$test->run(new HtmlReporter());
Are mock objects not generated as descendants of their respective classes?

Posted: Mon Oct 15, 2007 3:03 am
by Maugrim_The_Reaper
It should pass as far as I can see. Try using the deprecated is_a() function in case it's some PHP5 oddity.

Posted: Mon Oct 15, 2007 4:00 am
by Jenk
or perhaps adding superfluous parenthethis will help?

Code: Select all

$this->assertTrue(($book instanceof Item));

Posted: Mon Oct 15, 2007 10:12 am
by Luke
Nope, neither of those made any difference.

Posted: Mon Oct 15, 2007 11:08 am
by Jenk
try running it through reflection to see the hierachy.

Posted: Mon Oct 15, 2007 10:47 pm
by Luke
It tells me that MockMobyDick extends SimpleMock... shouldn't it extend MobyDick?

Posted: Tue Oct 16, 2007 3:32 am
by Maugrim_The_Reaper
Much as we hate to lose you - might be time to call in the SimpleTest team on their mailing list...;)

What version of ST and PHP5 are you using? I'll see if I can reproduce the problem here and trace it down.

Posted: Tue Oct 16, 2007 11:15 am
by Luke
OK, the same test passes without a problem on my work machine. I'll have to check on my laptop when I get a minute to see what version of simpletest I'm running on there... I'm actually not even sure what version of php I have on there. I'll let you know. Thanks a lot!

Posted: Tue Oct 16, 2007 12:15 pm
by Jenk
The Ninja Space Goat wrote:It tells me that MockMobyDick extends SimpleMock... shouldn't it extend MobyDick?
Does it implement mobydick?

Posted: Sat Oct 20, 2007 5:08 pm
by Chris Corbyn
Take a few steps back and think about this. You're trying to mock a class which has a concrete implementation. Mocking is something you should really be doing on interfaces (think of it as the most generic implementation of that "type"). If your code deals with Books and not just MobyDicks then you really should be mocking Book.

Generally I work in this order of preference:

Interface
Abstract Class
Concrete Class

Unit testing with mock objects becomes so much more straightforward when you're just applying interfaces. It also encourages dependency injection which in turn produces well written and flexible code.

I'd still like to see a really full-blown stand-alone mock object library for PHP. A colleague at work was talking about one of the Java mock object libraries (maybe it was EasyMock) and you essentially put the object into "record mode" then apply it:

Something like:

Code: Select all

$mock = new SomeMockObject();
$mock->startRecording();
$mock->thisWillBeInvoked();
$mock->andThis($withThisArg);
$mock->endRecording();

$whatever->doSomethingWithMy($mock);
EDIT | Jesus, was I drinking when I wrote that! :? I seem to have drifted way of course as my imagination ran away with me :P

Posted: Sat Oct 20, 2007 10:18 pm
by Luke
Thanks Chris. I think I'm finally starting to understand the concept of mocking, but I'm going to play around with it a little more. Every time I think I understand the concepts, I try writing the tests, I get one or two done, and then I run into something difficult and give up for a while. Well not this time! :)

Posted: Sat Oct 20, 2007 11:21 pm
by Ambush Commander
While PHP 5's built-in object hinting capabilities are nice, PHP is a dynamically typed language and I'd use it that way! Duck typing (if it acts like a duck, it is a duck) for the win.

Chris, that recording mock concept is really interesting. I wonder, however, how one would specify the return values for the function calls. Edit: It is indeed SimpleMock, as seen here http://www.easymock.org/EasyMock2_2_Documentation.html . Very cool.

Posted: Sun Oct 21, 2007 8:49 am
by Jenk
I realise this should be in it's own topic by now, but.. meh.

Using expectation objects for mocks handles the recording aspect easily, as I posted in Maurgrims thread regarding his new phpspec testing framework.

Code: Select all

$mock = new Mock('ClassName');
$expectation = $mock->expectCall('someMethod');
$expectation->acceptArgs($arrayOfArgs);
$expectation->expectCallCount(2);
To avoid any chance of method name collisions, I'd use a "proxy" object which takes the mock object as a constructor parameter, and has nothing else declared except __call(), __set() and __get(). I then pass this object as the dependency/mock. (This is what I use in Smalltalk, paraphrased in PHP)