Mocking an object - getting a strange error

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Mocking an object - getting a strange error

Post 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. :(
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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);
  }
}
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

or perhaps adding superfluous parenthethis will help?

Code: Select all

$this->assertTrue(($book instanceof Item));
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Nope, neither of those made any difference.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

try running it through reflection to see the hierachy.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

It tells me that MockMobyDick extends SimpleMock... shouldn't it extend MobyDick?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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!
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

The Ninja Space Goat wrote:It tells me that MockMobyDick extends SimpleMock... shouldn't it extend MobyDick?
Does it implement mobydick?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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! :)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post 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.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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)
Post Reply