Page 1 of 1
SimpleTest Mock - getArgumentsAt() ?
Posted: Thu Dec 28, 2006 7:12 am
by Chris Corbyn
I want to collect the arguments in a Mock object myself. I can make an identical expectation on the arguments passed by using expectAt() but this is too rigid here as I simply want to analyze a component in a passed object, not the entire object.
Does anybody know of a reasonable way to do something like this?
Code: Select all
Mock::Generate("SomeClass", "MockSomeClass");
class TestOfSomeClass extends UnitTestCase
{
public function testMessageHasTheToHeaderRemovedWhenPassed()
{
$blah = new Whatever();
$mock = new MockSomeClass();
$mock->expectOnce("someMethod");
$blah->setListener($mock);
$blah->doSomething();
//Here's the tricky bit
$passed_args = $mock->getArgumentsAt(0, "someMethod");
$this->assertFalse($passed_args[1]->hasTo());
}
}
I'm tempted to use a partial mock and copy the argument into a property before passing it to someMethod() but that feels awfully unclean.
Posted: Thu Dec 28, 2006 8:11 pm
by lastcraft
Hi.
This is a repost of the Sitepoint thread, but basically...
1) Extend the mock:
Code: Select all
Mock::generate('MyClass', '_MockMyClass');
class MockMyClass extends _MockMyClass {
function someMethod($object, ...) {
$test = SimpleTest::getContext()->getTest();
$test->assertFalse($object->hasTo());
return parent::someMethod($object, ...);
}
}
2) Create a custom expectation:
Code: Select all
class NotHasTo extends SimpleExpectation {
function test($object) {
return ! $object->hasTo();
}
}
The in the test...
Code: Select all
$mock->someMethod(new NotHasTo(), ...);
yours, Marcus
Posted: Fri Dec 29, 2006 5:06 am
by Chris Corbyn
Ha, yeah. Didn't wanna come back with [Solved] and a link to SitePoint being a moderator here and all that
Cheers dude.
Posted: Fri Dec 29, 2006 10:39 am
by John Cartwright
d11wtq wrote:Ha, yeah. Didn't wanna come back with [Solved] and a link to SitePoint being a moderator here and all that
Cheers dude.
Nothing wrong with that, all is fair in love and war.. and programming.

Posted: Wed Jan 03, 2007 10:45 am
by Ollie Saunders
This seems incredibly cool and I need to do such a thing right now.
What's the deal with the hasTo() stuff? Is that specific to this example?
Posted: Wed Jan 03, 2007 11:14 am
by Chris Corbyn
ole wrote:This seems incredibly cool and I need to do such a thing right now.
What's the deal with the hasTo() stuff? Is that specific to this example?
Yeah it was a method to check for a To: header in an email.
I just wrote custom mock objects to get around it, overriding the method I wanted to catch parameters from, with a version which makes assertions on the arguments.
Code: Select all
class CustomMailSendExpectation extends Swift_Plugin_MailSend
{
protected $testcase = null;
public function __construct($test)
{
parent::__construct();
$this->testcase = $test;
}
}
//Some custom expectations - ouch, yes, yes I know, perhaps I've dug myself into a hole here!
class MailSendExpectingNoTo extends CustomMailSendExpectation
{
public function doMail($to, $subject, $message, $headers)
{
if ($headers->has("To")) $this->testcase->fail("The headers should NOT have a To field in them");
else $this->testcase->pass();
}
}
// snip //
public function testHeadersDoNotContainThe_To_Field()
{
$swift = new Swift(new Swift_Connection_NativeMail());
$mailsend = new MailSendExpectingNoTo($this);
$swift->attachPlugin($mailsend, "_MAIL_SEND"); //Override the MailSend plugin with a custom mock
$recipients = new Swift_Address("test@bar.tld", "Test");
$sender = "foobar@bar.com";
$subject = "Foo Bar";
$body = "Foo test\r\nBar";
$message = new Swift_Message($subject, $body);
$swift->send($message, $recipients, $sender);
}
Marcus provided some more re-usable solutions though by extending the unit testing framework

The way expectations are handled is pretty neat.
Posted: Wed Jan 03, 2007 3:08 pm
by Ollie Saunders
I ended up doing this:
Code: Select all
<?php
require_once 'Osis/Form/Widget.php';
require_once 'Osis/Form/Validation/Interface.php';
require_once 'Osis/Form/Container/Master/Interface.php';
require_once 'Osis/Form/Renderer/IsA/Interface.php';
Mock::generate('Osis_Form_Validation_Interface','_Mock_Osis_Form_Validation');
Mock::generate('Osis_Form_Container_Master_Interface', 'Mock_Osis_Form_Container_Master_Interface');
Mock::generate('Osis_Form_Retriever_Interface', '_Mock_Osis_Form_Retriever');
Mock::generate('Osis_Form_Renderer_IsA_Interface', '_Mock_Osis_Form_Renderer');
class Mock_Osis_Form_Validation extends _Mock_Osis_Form_Validation
{
private $_expectedDispatch;
public function setExpectedDispatch($expectedDispatch)
{
$this->_expectedDispatch = $expectedDispatch;
}
public function dispatch($which)
{
$test = SimpleTest::getContext()->getTest();
$test->assertIdentical($which, $this->_expectedDispatch);
parent::dispatch($which);
}
}
class Mock_Osis_Form_Retriever extends _Mock_Osis_Form_Retriever
{
private $_expectedMaster;
public function setExpectedMaster($expectedMaster)
{
$this->_expectedMaster = $expectedMaster;
}
public function initialize(Osis_Form_Container_Master_Interface $master)
{
$test = SimpleTest::getContext()->getTest();
$test->assertIdentical($master, $this->_expectedMaster);
parent::initialize($master);
}
}
class Mock_Osis_Form_Renderer extends _Mock_Osis_Form_Renderer
{
private $_expectedInput;
public function setExpectedInput($expectedInput)
{
$this->_expectedInput = $expectedInput;
}
public function setDataFromEntity($data)
{
$test = SimpleTest::getContext()->getTest();
$test->assertIdentical($data->input, $this->_expectedInput);
parent::setDataFromEntity($data);
}
}
class Osis_Form_WidgetTest extends Osis_UnitTest
{
/**
* @var Osis_Form_Widget
*/
public $inst;
public function setUp()
{
$this->inst = new Osis_Form_Widget(
new Osis_Form_Id('foo'),
$this->mockValidation = new Mock_Osis_Form_Validation()
);
$this->inst->setRetriever(
$this->mockRetriever = new Mock_Osis_Form_Retriever()
);
$this->inst->setRenderer(
$this->mockRenderer = new Mock_Osis_Form_Renderer()
);
}
public function tearDown()
{
$this->mockValidation->tally();
$this->mockRetriever->tally();
$this->mockRenderer->tally();
}
public function testAll()
{
$this->mockRenderer->setReturnValue('render', $renderReturn = uniqid());
$this->mockRenderer->expectOnce('render');
$this->mockRenderer->setExpectedInput($inputReturn = uniqid());
$mockMaster = new Mock_Osis_Form_Container_Master_Interface();
$this->mockRetriever->setExpectedMaster($mockMaster);
$this->mockRetriever->expectOnce('initialize');
$this->mockRetriever->setReturnValue('getInput', $inputReturn);
$this->mockRetriever->expectCallCount('getInput', 2);
$this->mockValidation->setExpectedDispatch(Osis_Form_Widget::EVENT_INIT);
$this->mockValidation->expectCallCount('dispatch', 2);
$this->inst->initialize($mockMaster);
$this->mockValidation->setExpectedDispatch(Osis_Form_Widget::EVENT_RENDER);
$this->assertEqual($renderReturn, $this->inst->render());
$this->assertEqual($inputReturn, $this->inst->getInput());
}
}
That's testing an object that deals with a number of other objects (a retriever, renderer, master and validation object). It's lovely when it call comes together though. I must admit this was considerably harder to test than write.
An actual getArgumentsAt() method would be really nice in my opinion.
Posted: Wed Jan 03, 2007 6:46 pm
by lastcraft
Hi...
The example is quite complicated and I don't entirely understand it. Why could you not just place the expected object in the expectOnce() call? Is it because you want this to tie up with something another object passes in? I've kind of lost the plot on this one

.
yours, Marcus
Posted: Wed Jan 03, 2007 8:23 pm
by Ollie Saunders
Why could you not just place the expected object in the expectOnce() call?
Well Marcus, you just removed a whole lot of lines from my test. To answer your question: Because I hadn't RTFMed!
Is it because you want this to tie up with something another object passes in?
No its because I'm an idiot.
I've kind of lost the plot on this one Sad.
No I have, you're doing just fine.
I have a bit of a thing with mock objects....I read "The mock object pattern" in Jason Sweat's patterns book and he said they help you out when you are experiencing a "chicken and egg scenario" and you can use them to stand in for objects that do not yet exist and that really confused me because obviously Mock::generate() needs that all important first parameter. Annnyyway I think I'm finally getting to grips with them. As I understand it mocks are
- To test an object's interactions with another; without mocks this is difficult
- To simplify complex or expensive emulations with predefined returns such as standing in for a databse
- To make tests less fragile (I only realised today you can mock interfaces which is very nice)
Anyway here's my new code, see much better ahhhh:
Code: Select all
<?php
require_once 'Osis/Form/Widget.php';
require_once 'Osis/Form/Validation/Interface.php';
require_once 'Osis/Form/Container/Master/Interface.php';
require_once 'Osis/Form/Renderer/IsA/Interface.php';
Mock::generate('Osis_Form_Validation_Interface','Mock_Osis_Form_Validation');
Mock::generate('Osis_Form_Container_Master_Interface', 'Mock_Osis_Form_Container_Master_Interface');
Mock::generate('Osis_Form_Retriever_Interface', 'Mock_Osis_Form_Retriever');
Mock::generate('Osis_Form_Renderer_IsA_Interface', 'Mock_Osis_Form_Renderer');
class Osis_Form_WidgetTest extends Osis_UnitTest
{
/**
* @var Osis_Form_Widget
*/
public $inst;
public function setUp()
{
$this->inst = new Osis_Form_Widget(
new Osis_Form_Id('foo'),
$this->mockValidation = new Mock_Osis_Form_Validation()
);
$this->inst->setRetriever(
$this->mockRetriever = new Mock_Osis_Form_Retriever()
);
$this->inst->setRenderer(
$this->mockRenderer = new Mock_Osis_Form_Renderer()
);
}
public function tearDown()
{
$this->mockValidation->tally();
$this->mockRetriever->tally();
$this->mockRenderer->tally();
}
public function testAll()
{
$this->mockRetriever->expectOnce('initialize', array($mockMaster));
$this->mockRetriever->setReturnValue('getInput', $inputReturn = uniqid());
$this->mockRetriever->expectCallCount('getInput', 2);
$this->mockValidation->expectCallCount('dispatch', 2);
$this->mockValidation->expectAt(0, 'dispatch', array(Osis_Form_Widget::EVENT_INIT, $this->inst));
$this->mockValidation->expectAt(1, 'dispatch', array(Osis_Form_Widget::EVENT_RENDER, $this->inst));
$mockMaster = new Mock_Osis_Form_Container_Master_Interface();
$this->inst->initialize($mockMaster);
$this->mockRenderer->setReturnValue('render', $renderReturn = uniqid());
$this->mockRenderer->expectOnce('render');
$this->assertEqual($renderReturn, $this->inst->render());
$this->assertEqual($inputReturn, $this->inst->getInput());
}
}
Posted: Thu Jan 04, 2007 5:06 am
by sike
hey ole,
your test is hard to understand (at least for me) so i suggest splitting the testAll in more meaningful tests methods (eg. testWidgetReadsDataFromRetriever).
cheers
Chris
Posted: Thu Jan 04, 2007 5:59 am
by Ollie Saunders
so i suggest splitting the testAll in more meaningful tests methods (eg. testWidgetReadsDataFromRetriever).
I thought that was be really difficult to do but I guess I should give it a go. It is difficult to understand. It was quite difficult to write actually.
Posted: Thu Jan 04, 2007 10:51 am
by feyd
ole wrote:I thought that was be really difficult to do but I guess I should give it a go. It is difficult to understand. It was quite difficult to write actually.
That's often a "bad" smell.
