Page 1 of 2

The current mock object landscape.... bleak?

Posted: Mon Mar 10, 2008 3:22 am
by Chris Corbyn
I have to admit that the best mock object library currently available (read, complete) in PHP is the one from SimpleTest, but more and more I'm finding myself backed into corners testing implementation details with it. Specifically.... the sequence in which invokations occur.

Say you don't care how many times a mocked method is invoked, but you do care that it's called at least once with the argument "bar". There's no way to do that with SimpleTest except if you know the timing of the method call, which is a huge implementation detail nightmare :(

I'm a big fan of the interface of jMock where you create a Mockery, then add expectations in a fluent fashion.... and those expectations *do not* have to be in sequence unless specified. Additionally it returns default values which match the method's @return type.

We're also lacking the abaility to "perform actions" on parameters rather than just returning values. Take the scenario where you pass an object as a parameter and expect some invokation on that parameter. There are times when you want your mock to be able to "mock" that invokation rather than returning a value.

After this most recent project I'm working on I've decided I'd like to contribute toward a jMock (ish) clone for PHP. I know Pádraic was working on something, but I don't know what the status of that is.

With a following basic features in place I think a new standalone mock object library for PHP would be a great addition:

* Mock interfaces and maintain any type-hints on method signatures
* Mock classes and maintain the correct type (unless there are "final" methods which can't be overridden)
* Return default values where a docblock comment reveals @return in scope
* Allow invokations in any order
* Allow a sequence to be forced if required
* Using a fluent interface

I've already started a rough (first few hundred lines) code base for what I was planning on calling "YayMock" (as in jMock, but with a Yay!). It doesn't do anything yet but it will look something like this (stolen as closely as possible from jMock).

Code: Select all

interface SomeInterface {
  /**
    * @return Foo
    */
  makeFoo($arg);
}
 
$mockery = new Mockery();
 
$mockObj = $mockery->mock('SomeInterface');
 
$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')
  ->one($mockObj)->makeFoo('another arg')
  );
 
$foo1 = $mockObj->makeFoo('another arg'); //returns a Mocked instance of Foo
$foo2 = $mockObj->makeFoo('some arg'); //returns a different Mocked instance of Foo
 
//This throws some documented type of Exception the xUnit framework can wrap
$mockery->assertIsSatisfied();
Then if you want to override the default return value:

Code: Select all

$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')->will(returnValue($myFoo))
  ->one($mockObj)->makeFoo('another arg')
  );
If you wish to simply ignore invokations on all methods other than those specified:

Code: Select all

$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')
  ->ignoring($mockObj)
  );
If you want to ignore invokations of a particular method call:

Code: Select all

$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')->will(returnValue($myFoo))
  ->ignoring($mockObj)->makeFoo('another arg')
  );
If you want to allow a maximum number of calls matching a given invokation:

Code: Select all

$mockery->checking(
  Expectations::newInstance()
  ->atMost(3)->of($mockObj)->makeFoo('some arg')
  ->one($mockObj)->makeFoo('another arg')
  );
If you want to specify that calls come in an exact sequence:

Code: Select all

$seq = $mockery->createSequence('blah');
 
$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')->inSequence($seq)->will(returnValue($myFoo))
  ->one($mockObj)->makeFoo('another arg')->inSequence($seq)
  );
If you want to do something other than return a value (or in addition to):

Code: Select all

$mockery->checking(
  Expectations::newInstance()
  ->one($mockObj)->makeFoo('some arg')->will(new MyAction($something))
  ->one($mockObj)->makeFoo('another arg')
  );
Would anyone be interested? :) And would Pádraic be upset or willing to contribute?

I think jMock has the most flexible mock object API I've ever used.

Re: The current mock object landscape.... bleak?

Posted: Mon Mar 10, 2008 7:48 am
by Chris Corbyn
I'm actually making good progress on the groundwork for this. I think I'll continue with it until it's in a fit state to begin to show off (maybe a few days?) and then put it in a public svn repo so either:

a) Pádraic and I can battle it out :P
b) Pádraic and I can collaborate

I just want a flexible mock object library :(

Effectively the basis of my approach is:

* An Invocation inside a mock passes an Invocation objects to the context which created it
* The context asks all Expectations (not in the SimpleTest sense) if they match this Invocation
* If the Invocation matches, it specifies an Action to run
* When assertIsSatisfied() is called, all Expectation are checked for fulfillment.

Nice and simple.

I should add that the *only* methods in a mock object created with YayMock are the mocked ones (i.e. there is no possibility for a name clash and no methods are injected). The only fiddly bit is telling the mock to go into record mode while the Expectation is generated.

Ok, I'll shut up until I reveal some code now then :)

Re: The current mock object landscape.... bleak?

Posted: Mon Mar 10, 2008 11:05 am
by Christopher
Have you talked to Marcus and the SimpleTest folks? I know they are pretty open to things like this.

Re: The current mock object landscape.... bleak?

Posted: Mon Mar 10, 2008 11:29 am
by Maugrim_The_Reaper
Battle it is ;). hehehe

On a more serious note - one of the attractions of PHPMock and by return it's extended development progress is simply the number of stakeholders (Me (PHPSpec), Travis (PHPT), Sebastian (PHPUnit)). Around Christmas I had in place a basic operating 0.1 version for alpha testing and basically as a prototype - you can get this from the Google code SVN repo. I've subsequently expanded this towards stable behaviour and am just waiting primarily for some time to fix a previous prototyping error (primarily I made an error in specifying a behaviour and it led to false positive passes in the test suite).

The second holdup after many weeks of slow baking is a split in approaches to the API, which has simply exploded more into a debate over mocking/stubbing practices. Essentially I'm a Mockist (I mock anything where the interface being mocked is unknown/unpredictable/non-existent), whereas another is primarily a Stubbist. In PHP there are a LOT of stubbists since both PHPT and PHPUnit have no usable mocking system at all. SimpleTest users like us are spoiled.

My intention after PHPSpec 0.3 is released shortly (bug fix release pending the great Summer Zend and Story Runner development plan) is to simply take my advantage as the current sole active developer and run my mockist opinion to its inevitable conclusion as a rolled up 0.2 beta. The chips may fall wherever at that point - I literally need a stable PHPMock to make PHPSpec useful and I can't hold it up any longer.

The third holdup is a mixture of reduced development time (the Spring slowdown) and that I'm writing a book ;). More on that another day...

As with any language there are going to be two other diverting forces - whether to mock as in Java, or mock as in Ruby. I've almost exclusively adopted the Ruby style of mocking since a) I don't do Java anymore, b) Ruby testing is a fertile field of innovation and relies on fluent interfaces and DSLs, and c) Ruby and PHP while in competition share uses on the web and I also need a heavy focus on modules for web application MVC mocking of controller/model dependencies. The "I do both PHP and Rails and prefer familiarity over alienation" argument.

All this said - I am me (one). PHPMock is the expression of a single developer in a largely simplified form to allow for future behind the scenes refactoring/replacement. If you are interested in collaboration I'd invite you to first of all read the specs (which give all the major supported use cases for Iteration #1, and #2) and should give you a flavour of what stuff is completely missing right now (like public property mocking, mock interaction recording, and default Mocks to enable later concrete extenders). If after that you want to collaborate on PHPMock or continue your current path, that's entirely your call. I just wanted to write a long post since I haven't time for a monologue like this for at least a month on the forums ;). Just kidding.

I'd love a collaborator to balance my single minded approach, preferably if we can agree on what Mocks are and do. Use of PHPSpec while the current trend (only me, remember?) is optional - PHPMock's stated goal is to be independent of and non-reliant on any single testing/specing framework. Integration (such as with PHPUnit 4 in the future) is enabled but not required.

Cutting off this monologue now...

Re: The current mock object landscape.... bleak?

Posted: Mon Mar 10, 2008 6:11 pm
by Chris Corbyn
I have to admit, I didn't realise PHPMock was usable yet. I remember checking the code in Jab/Feb and seeing that it looked very slim so I just assumed it wasn't complete. I'll have to check it out because I wouldn't want to be doing something which is a wasted effort :)

Re: The current mock object landscape.... bleak?

Posted: Tue Mar 11, 2008 4:25 am
by Maugrim_The_Reaper
It looks so slim because it follows a pure Mockist route - essentially once you have added the basic code generation for a Mocked class, and assembled a storage class for expectations, you end up with not a lot of code. The rest of the required features then become an exercise in API design, and refactoring.

If you're looking for what my preferred target completed Mock Object library could look like from the API you should take a look at Ruby's flexmock or mocha. Hopefully directing anyone here to a Ruby library isn't taken the wrong way entirely :). Features beyond those found in the Ruby mocking community presently are intended to be pushed to after-stable releases.

Re: The current mock object landscape.... bleak?

Posted: Fri Mar 14, 2008 1:57 am
by Chris Corbyn
It needs refactoring, but I have successfully got the ground work for a jMock clone done. Interestingly I started off doing the whole hard-coded traditional mock thing in my tests, then as the system started to grow and the tests got refactored I gradually started to use the mock object library to test itself.

Things missing:

* It currently only mocks interfaces (it won't remain that way) [EDIT | Done]
* Sequence expectations [EDIT | Done]
* State machine [EDIT | Done]
* Various "Matchers" [EDIT | Done]
* The Description text for what went wrong [EDIT | Roughly done]

I expect another week or so's development would finish it off.

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

The main difference between using this over SimpleTest's mock object support is:

* No extra methods are added to mock objects for expectations (you use the context for that)
* Expectations are based on invocation signatures rather than timings
* Expectations can be specified with fairly loose rules and perform any Action

I haven't got any documentation together for it yet (except for a bit of text in the README) but since some of the Unit tests make use of it you can probably get a rough feel for the interface.

I kinda need to finish it so I can employ it in some of the flakier tests in Swift Mailer so I'll focus on it over the next week.

EDIT | If you check the code out of svn and are wondering how my test suite is put together (I wrap simpletest with my own test suite), the test cases can be found under "tests" and the tests can be run by either:

a) Running the command "php test-suite/run.php" on the command line (it'll look screwy on windows currently)
b) Opening "test-suite/index.php" in a web browser

Re: The current mock object landscape.... bleak?

Posted: Sat Mar 15, 2008 9:13 pm
by Chris Corbyn
Hoping to get a release of this out tonight, but one design decision I'm tossing around is when trying to mock any of the following:

a) A class declared as final
b) A class with a private constructor
c) A class with final public methods

Do I:

1) Throw a fatal error because they can't "truly" be mocked
2) Mock them without extending them and throw a warning
3) Mock them without extending them and remain silent (ala SimpleTest)

I think a warning is probably needed at least which the developer can acknowledge and supress if they wish to do so. The issue is that a type hint can no longer be enforced if the original class isn't extended and fully overridden.

Interfaces FTW!

EDIT | Went down a friendly warning route. The mock object is created, but on the first generation of the mock a warning explaining the loss of type is thrown. It means if you want to mock a final class you will have to deliberately supress the error:

Code: Select all

$mock = @$context->mock('AFinalClass');

Re: The current mock object landscape.... bleak?

Posted: Sat Mar 15, 2008 10:21 pm
by Christopher
Chris Corbyn wrote:EDIT | Went down a friendly warning route. The mock object is created, but on the first generation of the mock a warning explaining the loss of type is thrown. It means if you want to mock a final class you will have to deliberately supress the error:
You have always been too nice! ;)

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 9:00 am
by lastcraft
Hi...

Would you believe I had a fluent mock interface two years ago :(. It was very much a lab bench experiment and also based on JMock. It never found it's way into SimpleTest, because it was impossible to make it PHP4 compatible. The vast bulk of the community were PHP4 based then.

But from now onwards (1.0.1 is out, 1.1 coming), SimpleTest no longer supports PHP4. I'm free.

So that means I'm looking to build a whole new, much more integrated, mock API. The old one will be supported as a plug-in so that everyone's tests still work, but the new one will be fluent, and can be a whole lot more flexible. It still has to be simple to use for beginners though, which means a full JMock API may not happen if it starts to add overhead.

We need to get a PHP5 only 1.1 out to assist people in migrating. Then we start building the plug-in version of SimpleTest that will allow us to run legacy code concurrently with the new stuff. After that everything gets a rewrite.

The timescale is probably some 4-6 months away. Enough time for you to finish your more functionally complete Mockery version. After that, would you like to collaborate? In particular, would you consider joining the SimpleTest development team?

yours, Marcus

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 9:25 am
by lastcraft
Hi...
Chris Corbyn wrote: * No extra methods are added to mock objects for expectations (you use the context for that)
I'm going the same way.
Chris Corbyn wrote: * Expectations are based on invocation signatures rather than timings
This works for return values now. Can you give an example of what you wanted, and a code snippet of Yaymock? I'm not sure how you distinguish the expectation from the selector.
Chris Corbyn wrote: * Expectations can be specified with fairly loose rules and perform any Action
Could you post an example snippet of how this works. I couldn't find an elegant way to do this in PHP, and ended up with a different flex point instead, namely the ability to subclass the mock. Subclassing was super flexible, and you need no special mechanisms to add extra behaviour, but in practice no one did it. I don't understand why, but in the meantime I have a whole use case which never worked out. i want to fix this next time around.

yours, Marcus

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 11:42 am
by Chris Corbyn
lastcraft wrote:Hi...
Chris Corbyn wrote: * No extra methods are added to mock objects for expectations (you use the context for that)
I'm going the same way.
Excellent :D
lastcraft wrote:
Chris Corbyn wrote: * Expectations are based on invocation signatures rather than timings
This works for return values now. Can you give an example of what you wanted, and a code snippet of Yaymock? I'm not sure how you distinguish the expectation from the selector.
Not 100% what you mean here. I'll give a comparison. Say I have a Response class and I expect setHeader('Location', 'http://google.com') and also setHeader('Status', '302') in SimpleTest I'd have to expect them in the correct order and at very specific timings (i.e. any X-Header setting would cause a fragile test).

This is how I'd enforce that both of those headers are set with SimpleTest in the least fragile way I can come up with:

Code: Select all

Mock::generate('Response', 'MockResponse');
$response = new MockResponse();
$response->expectAt(0, 'setHeader', array('Location', 'http://google.com'));
$response->expectAt(1, 'setHeader', array('Status', '302'));
$response->expectMinimumCallCount('setHeader', 2); //Seems to be needed if the above is enforced?
And in Yay! Mock (yes, it is more verbose)...

Code: Select all

$context = new Yay_Mockery();
$response = $context->mock('Response');
$context->checking(Yay_Expectations::create()
  -> one($response)->setHeader('Location', 'http://google.com')
  -> one($response)->setHeader('Status', '302')
  -> ignoring($response) //I'm still undecided if this should be implicit
  );
My call to ignoring() means that any other invocations are ignored (like SimpleTest). Without it the test will fail since the invocation will be assumed to be unexpected. I'm undecided whether or not to make this implicit and thereforce expect users to call never() if they want that behaviour. jMock does it my current way.

Ok, they both do the same thing provided the location is set before the status and they are both the first calls to setHeader(). Now if I was to call setHeader('X-SystemVersion', '1.0.1') before the location is set SimpleTest will fail (I'd consider that a fragile test). I've hit the same hurdle several times. Also, if status is set before Location the test will fail in SimpleTest, but not in Yay! Mock. This is since Yay is simply looking for matches on invocations and not on timings.

If you do need timings Yay! provides Sequences so I can get the same overall (fragile in the specific case?) behaviour as SimpleTest by doing:

Code: Select all

$context = new Yay_Mockery();
$s = $context->sequence('Setting headers');
$response = $context->mock('Response');
$context->checking(Yay_Expectations::create()
  -> one($response)->setHeader('Location', 'http://google.com') -> inSequence($s)
  -> one($response)->setHeader('Status', '302') -> inSequence($s)
  -> ignoring($response) //I'm still undecided if this should be implicit
  );
lastcraft wrote:
Chris Corbyn wrote: * Expectations can be specified with fairly loose rules and perform any Action
Could you post an example snippet of how this works. I couldn't find an elegant way to do this in PHP, and ended up with a different flex point instead, namely the ability to subclass the mock. Subclassing was super flexible, and you need no special mechanisms to add extra behaviour, but in practice no one did it. I don't understand why, but in the meantime I have a whole use case which never worked out. i want to fix this next time around.
The innards or the public facing bit?

Code: Select all

class MyAction implements Yay_Action {
  public function &invoke(Yay_Invocation $invocation) {
    $args =& $invocation->getArguments();
    $method = $invocation->getMethod(); //Not usually needed
    $object = $invocation->getObject(); //Not usually needed
    //I can do anything I like with these references here
    return $someting; //this value is returned from the mock method
  }
}
 
// .... 
 
$context->checking(Yay_Expectations::create()
  -> atLeast(2)->of($obj)->blah() -> will(new MyAction())
  );
The actual internal workings of that are effectively just based on a chain of command.

I'm more than happy to answer any questions you have and/or give feedback/thoughts on anything you're planning :) In fact, it would be a privilege.

PS: My beta code needs some serious refactoring/decoupling.

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 3:00 pm
by lastcraft
Hi...
Chris Corbyn wrote: This is how I'd enforce that both of those headers are set with SimpleTest in the least fragile way I can come up with:
OK, now I understand. I am a bit of a control freak when it comes to testing. Probably because of my electronics background - CMOS anyone :banghead: ? I tend to make my tests as deterministic as possible, which means the mocks tend to be a bit over controlling.
Chris Corbyn wrote: And in Yay! Mock (yes, it is more verbose)...
The JMock interface is the way it is, because within an IDE the auto-complete features reduce the typing workload and act as context sensitive help. Outside of that environment, lot's of small methods seem a bit of a hindrance. I was going to go for a coarser granularity...

Code: Select all

 
function testOnlyLegitResponseesAllowed() {
    $this->MockResponse
                ->setHeader('Location', '*')->once
                ->or->setHeader('Status', 302)->once;
    $response = new MockResponse();
}
 
Essentially I just have statements where the subject is the method call (brackets and arguments, and are treated as an expectation unless square brackets are used), the verb is one or more of returns/gives, throws, calls. the object is the argument of the verb, e.g. gives('Hello'). Adverbs include: once, always, never, atLeastOnce, fromNow, everMore, first, second, third, fourth. Optional conjunctions between statements include: and, or, before, immediatelyBefore, implies. I haven't worked out the full language yet, but I want it small.

I want to use fluency to cut down on the $this', not just 'cos it's fashionable to do DSL's this way. I am not denegrating fashion here, as familiarity is important in an interface. It's just that I want to get a typical mock set up from about three lines to a one liner. That would significantly shrink the size of peoples tests when you have three or four mocks to set up.
Chris Corbyn wrote: The innards or the public facing bit?
Always the interface .
Chris Corbyn wrote:

Code: Select all

class MyAction implements Yay_Action...
I can't help thinking there has to be a better way. I know PHP does not have lambdas/closures, but creating a whole class has to be overkill.

How about the test case itself is provides the enclosing scope? Then a callback could simply be a method on the test case...

Code: Select all

 
class MyTest extends UnitTestCase {
    function testSomethingNeedingCallback() {
        $this->MockGeekaliser->greet()->calls('me');
        $geek = new MockGeekaliser();
        $greeting = $geek->greet('Hello');
        ...
    }
 
    function me($mock, $message) {
        return $message . ' world';
    }
}
 
You still have to have an extra method, but this is a lot less overhead than an entire class.

yours, Marcus

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 7:07 pm
by Chris Corbyn
lastcraft wrote:OK, now I understand. I am a bit of a control freak when it comes to testing. Probably because of my electronics background - CMOS anyone :banghead: ? I tend to make my tests as deterministic as possible, which means the mocks tend to be a bit over controlling.
Gotcha, but I still maintain that it a cause of a fragile test ;)
lastcraft wrote:
Chris Corbyn wrote: And in Yay! Mock (yes, it is more verbose)...
The JMock interface is the way it is, because within an IDE the auto-complete features reduce the typing workload and act as context sensitive help. Outside of that environment, lot's of small methods seem a bit of a hindrance. I was going to go for a coarser granularity...

Code: Select all

 
function testOnlyLegitResponseesAllowed() {
    $this->MockResponse
                ->setHeader('Location', '*')->once
                ->or->setHeader('Status', 302)->once;
    $response = new MockResponse();
}
 
I'm really enjoying this discussion now Have you looked at PHPMock? (wondering if Maugrim is going to chime back in here). It's great to see quite a few different people are putting a lot of effort into producing something for everyone to use. Doing some reasearch I've stumbled upon AMock (didn't look great to me... not a fan of EasyMock), PHPMock and SnapTest (New on the landscape). Are we seeing a revolution?

My editor (TextMate) has handy shortcuts you can customize and JEdit does too. I've been using Yay! Mock in my massive set of test cases for Swift Mailer version 4 and I haven't really found it to be much of a hinderance but I do agree that you ideally want the least clutter possible in a test. It's a matter of finding a balance between:

a) Ease of use
b) Readability
c) Test Stability

There aren't any bugs in what my code does right now (that I'm aware of) so that's not the reason it's on beta... I'm fiddling with the interface a bit to try and trim out "fluff". Wouldn't want people using someting production-ready when the API may pick up some breaking changes.

On thing I plan on adding is something of the style:

Code: Select all

$response = $context->mock('Response');
$context->expecting()
  -> one($response)->setHeader('Location', 'http://google.com')
  -> one($response)->setHeader('Status', '302')
  ;
 
I assume your "or" on your code allows either one or the other but not both invocations? And if you used "and" would they have to come in that order? This was 90% of my reasoning for starting my own project and having that timing constraint slackened would possibly encourage me back again (Especially if Yay! doesn't pick up on interest... nobody wants to be using a testing tool others don't know how to use ).
lastcraft wrote:Essentially I just have statements where the subject is the method call (brackets and arguments, and are treated as an expectation unless square brackets are used), the verb is one or more of returns/gives, throws, calls. the object is the argument of the verb, e.g. gives('Hello'). Adverbs include: once, always, never, atLeastOnce, fromNow, everMore, first, second, third, fourth. Optional conjunctions between statements include: and, or, before, immediatelyBefore, implies. I haven't worked out the full language yet, but I want it small.
Yay!'s list more or less come from jMock. I'll try and show all the cardinalities here:

Code: Select all

$context->checking(Yay_Expectations::create()
  -> one($mock)->foo()
  -> exactly(2)->of($mock)->bar()
  -> atMost(7)->of($mock)->meh()
  -> atLeast(1)->of($mock)->blah()
  -> between(2, 5)->of($mock)->test()
  -> allowing($mock)->something() //same*
  -> ignoring($mock)->another() //same*
  -> never($mock)->wtf()
  );
Any behaviours (returning values, throwing exceptions, performing actions etc) immediately follow those cardinality clauses.
lastcraft wrote:I want to use fluency to cut down on the $this', not just 'cos it's fashionable to do DSL's this way. I am not denegrating fashion here, as familiarity is important in an interface. It's just that I want to get a typical mock set up from about three lines to a one liner. That would significantly shrink the size of peoples tests when you have three or four mocks to set up.
Definitely Still thinking of ways to keep Yay! Mocks behaviour and simplify the interface. The expectation setting is already fluent but things around it can be simplified futher I think. I already got a major (non-breaking) API change for the 3rd beta release. Mostly it's just adding wrapper functionality.

I'm curious how well your new stuff will stand outside of SimpleTest? Is $this->MockResponse created in setUp() or is something going to be built into one of the base classes which handles this via __set() or such like?

[EDIT | Note that you don't need to call $context->checking() more than once per test method... you can check multiple mocks in the same call; or you can use separate calls if needed:

Code: Select all

$context->checking(Yay_Expectations::create()
  -> one($mock1)->foo() -> returns('bar')
  -> one($mock2)->zip() -> returns('button')
  );
]
lastcraft wrote:
Chris Corbyn wrote:

Code: Select all

class MyAction implements Yay_Action...
I can't help thinking there has to be a better way. I know PHP does not have lambdas/closures, but creating a whole class has to be overkill.

How about the test case itself is provides the enclosing scope? Then a callback could simply be a method on the test case...

Code: Select all

 
class MyTest extends UnitTestCase {
    function testSomethingNeedingCallback() {
        $this->MockGeekaliser->greet()->calls('me');
        $geek = new MockGeekaliser();
        $greeting = $geek->greet('Hello');
        ...
    }
 
    function me($mock, $message) {
        return $message . ' world';
    }
}
 
You still have to have an extra method, but this is a lot less overhead than an entire class.
You almost exactly described what's already available

http://code.google.com/p/yaymock/wiki/E ... a_callback

That's just a wrapper around a CallbackAction implementing the Yay_Action interface. It looks like this:

Code: Select all

$context->checking(Yay_Expectations::create()
  -> exactly(3)->of($mock)->foo() -> calls($myCallback)
  );
Comes in handy since you can add a method to your test case for the callback. This is one place I really wish PHP had anonymous classes though. My implementation does just pass the Invocation parameter object however. I guess it would be nice to provide a version which has the same signature (i.e. it takes in a parameter list).

Re: The current mock object landscape.... bleak?

Posted: Sat Apr 12, 2008 7:27 pm
by Chris Corbyn
lastcraft wrote:

Code: Select all

function testOnlyLegitResponseesAllowed() {
    $this->MockResponse
                ->setHeader('Location', '*')->once
                ->or->setHeader('Status', 302)->once;
    $response = new MockResponse();
}
I'm curious how you distinguish between different instances of $response now? Unless you end up one mock class per object?