Any point in Unit Testing in this scenario?

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
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Maybe I'm missing something, but I've scanned over the Mock Objects page (mock_objects_documentation.html) a few times now and I see no mention of why this behaviour happens.

I need to create a Mock of a FakePlugin class so that I can track the number of times methods are called, when they are called and possibly some other things.

I wrote this class, which is a skeleton of a Swift plugin:

Code: Select all

<?php

class FakePlugin implements Swift_IPlugin
{
	public $pluginName = 'Fake';
	private $swift;
	
	public function loadBaseObject(&$swift)
	{
		$this->swift =& $swift;
	}
}

?>
I then tried mocking it:

Code: Select all

//Unfinished of course!
	public function testLoadingPlugin()
	{
		Mock::generate('FakePlugin');
		$plugin_count1 = $this->swift->numPlugins();
		$this->swift->loadPlugin(new MockFakePlugin($this));
		$plugin_count2 = $this->swift->numPlugins();
		$this->assertTrue($plugin_count1 < $plugin_count2);
	}
But I get this error when Swift tries to read the pluginName property....

Code: Select all

Exception: testLoadingPlugin -> Unexpected PHP error [Undefined property: MockFakePlugin::$pluginName] severity [E_NOTICE] in [/home/d11wtq/public_html/Swift-2.1.12/Swift.php] line [646]
Exception: testLoadingPlugin -> Unexpected PHP error [Undefined property: MockFakePlugin::$pluginName] severity [E_NOTICE] in [/home/d11wtq/public_html/Swift-2.1.12/Swift.php] line [647]
Exception: testLoadingPlugin -> Unexpected PHP error [Undefined property: MockFakePlugin::$pluginName] severity [E_NOTICE] in [/home/d11wtq/public_html/Swift-2.1.12/Swift.php] line [649]
The mock has deleted the property.... I don't get it :( It's a good indication I should have a getter for that purpose but still, where did my property go? :(
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

method_exists() doesn't work on the mock neither.... The whole way the plugins work is based around this method so this basically means it's not possible for me to mock plugins? Am I right?
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Hmm... this might mean you'd have to abstract the reflection.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Ambush Commander wrote:Hmm... this might mean you'd have to abstract the reflection.
Just discussed it with Marcus on SitePoint. The Mocks don't implement properties yet apparently so you need to make setters for all mocks and set them up yourself. Grr... fair enough. he said PHP5 is making it easier though since he has __get() and __set().

The method_exists() solution came to me whilst in discussion.... just extend the mock. I'm not sure what level of control you have over the methods in the extension though (i.e. if you can apply expectOnce() to methods in the subclass). I'll post back.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Followup: The mock cannot count the number of times methods in a subclass are called. Is it just me or are mocks a little flaky? You can't mock singletons neither, although that's clearly a more fundamental issue.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Realistically speaking, mocks are convenience, an automated way to generate something useful. If the framework proves limiting, you'll have to code it yourself, sometimes not inheriting from the base Mock class at all.

I do this for Singletons: I allow the sole instance to be overridden by the user for testing purposes, where they put in whatever global mock object they need. This is built into the class, so the test harness interfaces with directly and doesn't mock the singleton, in a way, it already is a mock.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

For testing purposes I did this.... and it works. It doesn't feel "too bad".

Code: Select all

<?php

//Look to the subclass MockFakePluginWithEvents for
// the implementation of these methods
class FakePlugin implements Swift_IPlugin
{
	public function loadBaseObject(&$swift) {}
	public function onLoad() {}
	public function onSend() {}
	public function onBeforeSend() {}
	public function onBeforeCommand() {}
	public function onCommand() {}
	public function onResponse() {}
	public function onUnload() {}
	public function onClose() {}
	public function onError() {}
	public function onFail() {}
	public function onLog() {}
	public function onConnect() {}
	public function onAuthenticate() {}
}

?>

Code: Select all

<?php

//Ignore the calls to parent::<method>()
// ... they are purely to notify the mock
class MockFakePluginWithEvents extends MockFakePlugin
{
	public $pluginName = 'Fake';
	public $swift;
	
	public function loadBaseObject(&$swift)
	{
		parent::loadBaseObject(&$swift);
		$this->swift =& $swift;
	}
	
	public function callSwiftAddCc($address)
	{
		$this->swift->addCc($address);
	}
	
	public function onLoad()
	{
		parent::onLoad();
	}
	
	public function onSend()
	{
		parent::onSend();
	}
	
	public function onBeforeSend()
	{
		parent::onBeforeSend();
	}
	
	public function onBeforeCommand()
	{
		parent::onBeforeCommand();
	}
	
	public function onCommand()
	{
		parent::onCommand();
	}
	
	public function onResponse()
	{
		parent::onResponse();
	}
	
	public function onUnload()
	{
		parent::onUnload();
	}
	
	public function onClose()
	{
		parent::onClose();
	}
	
	public function onError()
	{
		parent::onError();
	}
	
	public function onFail()
	{
		parent::onFail();
	}
	
	public function onLog()
	{
		parent::onLog();
	}
	
	public function onConnect()
	{
		parent::onConnect();
	}
	
	public function onAuthenticate()
	{
		parent::onAuthenticate();
	}
}

?>
And and example of where it was used (or three):

Code: Select all

public function testLoadingAndRemovingPlugin()
	{
		Mock::generate('FakePlugin');
		require_once 'MockFakePluginWithEvents.php';
		$plugin = new MockFakePluginWithEvents($this);
		
		$plugin_count1 = $this->swift->numPlugins();
		$this->swift->loadPlugin($plugin);
		$plugin_count2 = $this->swift->numPlugins();
		$this->assertTrue($plugin_count1 < $plugin_count2);
		
		$this->swift->removePlugin('Fake');
		$plugin_count3 = $this->swift->numPlugins();
		$this->assertTrue($plugin_count3 < $plugin_count2);
	}
	
	public function testPluginEventCalls()
	{
		Mock::generate('FakePlugin');
		require_once 'MockFakePluginWithEvents.php';
		$plugin = new MockFakePluginWithEvents($this);
		
		$plugin->expectOnce('loadBaseObject', array($this->swift));
		$plugin->expectOnce('onLoad');
		$plugin->expectOnce('onUnload');
		$plugin->expectOnce('onBeforeSend');
		$plugin->expectOnce('onSend');
		$plugin->expectAtLeastOnce('onBeforeCommand');
		$plugin->expectAtLeastOnce('onCommand');
		$plugin->expectAtLeastOnce('onResponse');
		$plugin->expectAtLeastOnce('onLog');
		
		$this->swift->loadPlugin($plugin);
		
		$this->swift->send('a@b', 'd@c', 'Subj', 'Body');
		
		$this->swift->removePlugin('Fake');
	}
	
	public function testPluginAccessToParent()
	{
		Mock::generate('FakePlugin');
		require_once 'MockFakePluginWithEvents.php';
		$plugin = new MockFakePluginWithEvents($this);
		
		$this->swift->loadPlugin($plugin);
		
		$cc_count1 = count($this->swift->getCcAddresses());
		$this->swift->getPlugin('Fake')->callSwiftAddCc('foo@bar');
		$cc_count2 = count($this->swift->getCcAddresses());
		$this->assertTrue($cc_count1 < $cc_count2);
		
		$this->swift->removePlugin('Fake');
		
		$this->swift->flushCc();
	}
Post Reply