*/
/**
- * Unit test suite for Pherge_Plugin classes
+ * Unit test suite for plugin classes.
*
* @category Phergie
* @package Phergie_Tests
abstract class Phergie_Plugin_TestCase extends PHPUnit_Framework_TestCase
{
/**
- * @var Phergie_Event_Handler
+ * Mock configuration
+ *
+ * @var Phergie_Config
+ */
+ protected $config;
+
+ /**
+ * Associative array for configuration setting values, accessed by the
+ * mock configuration object using a callback
+ *
+ * @var array
*/
- protected $handler;
+ protected $settings = array();
/**
+ * Mock connection
+ *
* @var Phergie_Connection
*/
protected $connection;
/**
- * @var array
+ * Mock event handler
+ *
+ * @var Phergie_Event_Handler
*/
- protected $eventArgs;
+ protected $events;
/**
+ * Mock plugin handler
+ *
+ * @var Phergie_Plugin_Handler
+ */
+ protected $plugins;
+
+ /**
+ * Plugin instance being tested
+ *
* @var Phergie_Plugin_Abstract
*/
protected $plugin;
/**
- * @var array
+ * Full name of the plugin class being tested, may be explicitly
+ * specified in subclasses but is otherwise automatically derived from
+ * the test case class name
+ *
+ * @var string
+ */
+ protected $pluginClass;
+
+ /**
+ * User nick used in any events requiring one
+ *
+ * @var string
+ */
+ protected $nick = 'nick';
+
+ /**
+ * Event source used in any events requiring one
+ *
+ * @var string
*/
- protected $config = array();
+ protected $source = '#channel';
/**
- * Constructs a test case with the given name.
+ * Initializes instance properties.
*
- * @param string $name
- * @param array $data
- * @param string $dataName
+ * @return void
*/
- public function __construct($name = NULL, array $data = array(), $dataName = '')
+ public function setUp()
{
- parent::__construct($name, $data, $dataName);
- $this->connection = new Phergie_Connection();
- $this->handler = new Phergie_Event_Handler();
+ if (empty($this->pluginClass)) {
+ $this->pluginClass = preg_replace('/Test$/', '', get_class($this));
+ }
+
+ if (empty($this->plugin)) {
+ $this->plugin = new $this->pluginClass;
+ }
+
+ $this->plugin->setConfig($this->getMockConfig());
+ $this->plugin->setConnection($this->getMockConnection());
+ $this->plugin->setEventHandler($this->getMockEventHandler());
+ $this->plugin->setPluginHandler($this->getMockPluginHandler());
}
/**
- * Assert that a given event type exists in the event handler
- * @param string $event
- * @param string $message
+ * Destroys all initialized instance properties.
+ *
+ * @return void
*/
- public function assertHasEvent($event, $message = null)
+ public function tearDown()
{
- self::assertTrue($this->handler->hasEventOfType($event), $message);
+ unset(
+ $this->plugins,
+ $this->events,
+ $this->connection,
+ $this->config,
+ $this->plugin
+ );
}
/**
- * Assert that a given event type DOES NOT exist in the event handler
- * @param string $event
- * @param string $message
+ * Returns a mock configuration object.
+ *
+ * @return Phergie_Config
*/
- public function assertDoesNotHaveEvent($event, $message = null)
+ protected function getMockConfig()
{
- self::assertFalse($this->handler->hasEventOfType($event), $message);
+ if (empty($this->config)) {
+ $this->config = $this->getMock('Phergie_Config', array('offsetExists', 'offsetGet'));
+ $this->config
+ ->expects($this->any())
+ ->method('offsetExists')
+ ->will($this->returnCallback(array($this, 'configOffsetExists')));
+ $this->config
+ ->expects($this->any())
+ ->method('offsetGet')
+ ->will($this->returnCallback(array($this, 'configOffsetGet')));
+ }
+ return $this->config;
}
/**
- * Assert that the emitter of the given command event was the given
- * plugin
+ * Returns whether a specific configuration setting has a value. Only
+ * intended for use by this class, but must be public for PHPUnit to
+ * call them.
+ *
+ * @param string $name Name of the setting
*
- * @param Phergie_Event_Command $event
- * @param Phergie_Plugin_Abstract $plugin
- * @param string $message
+ * @return boolean TRUE if the setting has a value, FALSE otherwise
*/
- public function assertEventEmitter(Phergie_Event_Command $event,
- Phergie_Plugin_Abstract $plugin,
- $message = null)
+ public function configOffsetExists($name)
{
- $this->assertSame($plugin, $event->getPlugin(), $message);
+ return isset($this->settings[$name]);
}
/**
- * Gets the events added to the handler by the plugin
- * @param string $type
- * @return array | null
+ * Returns the value of a specific configuration setting. Only intended
+ * for use by this class, but must be public for PHPUnit to call them.
+ *
+ * @param string $name Name of the setting
+ *
+ * @return mixed Value of the setting
*/
- public function getResponseEvents($type = null)
+ public function configOffsetGet($name)
{
- if (is_string($type) && strlen($type) > 0) {
- return $this->handler->getEventsOfType($type);
- }
- return $this->handler->getEvents();
+ return $this->settings[$name];
}
/**
- * Sets the event for the test
- * @param array $event
- * @param array $eventArgs
+ * Returns a mock connection object.
+ *
+ * @return Phergie_Connection
*/
- public function setEvent(array $event, array $eventArgs = null)
+ protected function getMockConnection()
{
- $eventClass = 'Phergie_Event_Request';
- if (is_array($event)) {
- $eventClass = $event[0];
- $eventType = $event[1];
- } else {
- throw new InvalidArgumentException("Invalid value for \$event");
+ if (empty($this->connection)) {
+ $this->connection = $this->getMock('Phergie_Connection');
+ $this->connection
+ ->expects($this->any())
+ ->method('getNick')
+ ->will($this->returnValue($this->nick));
}
- $event = new $eventClass();
- $event->setType($eventType);
- $event->setArguments($eventArgs);
- $this->plugin->setEvent($event);
- $this->eventArgs = $eventArgs;
+ return $this->connection;
}
/**
- * Sets the plugin to be tested
- * If a plugin requries config for testing, an array placed in
- * $this->config will be parsed into a Phergie_Config object and
- * attached to the plugin
+ * Returns a mock event handler object.
+ *
+ * @return Phergie_Event_Handler
*/
- protected function setPlugin(Phergie_Plugin_Abstract $plugin)
+ protected function getMockEventHandler()
{
- $this->plugin = $plugin;
- $this->plugin->setEventHandler($this->handler);
- $this->plugin->setConnection($this->connection);
- $this->connection->setNick('test');
- if (!empty($this->config)) {
- $config = new Phergie_Config();
- foreach ($this->config as $configKey => $configValue) {
- $config[$configKey] = $configValue;
- }
- $plugin->setConfig($config);
+ if (empty($this->events)) {
+ $this->events = $this->getMock('Phergie_Event_Handler', array('addEvent'));
}
+ return $this->events;
}
/**
- * Overrides the runTest method to add additional annotations
- * @return PHPUnit_Framework_TestResult
+ * Returns a mock plugin handler object.
+ *
+ * @return Phergie_Plugin_Handler
*/
- protected function runTest()
+ protected function getMockPluginHandler()
{
- if (null === $this->plugin) {
- throw new RuntimeException(
- 'Tests cannot be run before plugin is set'
+ if (empty($this->plugins)) {
+ $config = $this->getMockConfig();
+ $events = $this->getMockEventHandler();
+ $this->plugins = $this->getMock(
+ 'Phergie_Plugin_Handler',
+ array(), // mock everything
+ array($config, $events)
);
}
-
- // Clean the event handler... important!
- $this->handler->clearEvents();
-
- $info = $this->getAnnotations();
- $event = null;
- $eventArgs = array();
- if (isset($info['method']['event']) && isset($info['method']['event'][0])) {
- if (!is_string($info['method']['event'][0])) {
- throw new InvalidArgumentException(
- 'Only one event may be specified'
- );
- }
- $event = $info['method']['event'][0];
+ return $this->plugins;
+ }
- if (stristr($event, '::')) {
- $event = explode('::', $event);
+ /**
+ * Returns a mock event object.
+ *
+ * @param string $type Event type
+ * @param array $args Optional associative array of event arguments
+ * @param string $nick Optional user nick to associate with the event
+ * @param string $source Optional user nick or channel name to associate
+ * with the event as its source
+ *
+ * @return Phergie_Event_Request
+ */
+ protected function getMockEvent($type, array $args = array(),
+ $nick = null, $source = null
+ ) {
+ $methods = array('getNick', 'getSource');
+ foreach (array_keys($args) as $arg) {
+ if (is_int($arg) || ctype_digit($arg)) {
+ $methods[] = 'getArgument';
+ } else {
+ $methods[] = 'get' . ucfirst($arg);
}
}
- if (isset($info['method']['eventArg'])) {
- $eventArgs = $info['method']['eventArg'];
+
+ $event = $this->getMock(
+ 'Phergie_Event_Request',
+ $methods
+ );
+
+ $nick = $nick ? $nick : $this->nick;
+ $event
+ ->expects($this->any())
+ ->method('getNick')
+ ->will($this->returnValue($nick));
+
+ $source = $source ? $source : $this->source;
+ $event
+ ->expects($this->any())
+ ->method('getSource')
+ ->will($this->returnValue($source));
+
+ foreach ($args as $key => $value) {
+ if (is_int($key) || ctype_digit($key)) {
+ $event
+ ->expects($this->any())
+ ->method('getArgument')
+ ->with($key)
+ ->will($this->returnValue($value));
+ } else {
+ $event
+ ->expects($this->any())
+ ->method('get' . ucfirst($key))
+ ->will($this->returnValue($value));
+ }
}
- if (null !== $event) {
- $this->setEvent($event, $eventArgs);
+
+ return $event;
+ }
+
+ /**
+ * Sets the value of a configuration setting.
+ *
+ * @param string $setting Name of the setting
+ * @param mixed $value Value for the setting
+ *
+ * @return void
+ */
+ protected function setConfig($setting, $value)
+ {
+ $this->settings[$setting] = $value;
+ }
+
+ /**
+ * Returns the absolute path to the Phergie/Plugin directory. Useful in
+ * conjunction with getMockDatabase().
+ *
+ * @param string $subpath Optional path to append to the directory path
+ *
+ * @return string Directory path
+ */
+ protected function getPluginsPath($subpath = null)
+ {
+ $path = realpath(dirname(__FILE__) . '/../../../Phergie/Plugin');
+ if (!empty($subpath)) {
+ $path .= '/' . ltrim($subpath, '/');
}
+ return $path;
+ }
+
+ /**
+ * Modifies the event handler to include an expectation of an event
+ * being added by the plugin being tested. Note that this must be called
+ * BEFORE executing the plugin code intended to initiate the event.
+ *
+ * @param string $type Event type
+ * @param array $args Optional enumerated array of event arguments
+ *
+ * @return void
+ */
+ protected function assertEmitsEvent($type, array $args = array())
+ {
+ $this->events
+ ->expects($this->at(0))
+ ->method('addEvent')
+ ->with($this->plugin, $type, $args);
+ }
- $testResult = parent::runTest();
+ /**
+ * Modifies the event handler to include an expectation of an event NOT
+ * being added by the plugin being tested. Note that this must be called
+ * BEFORE executing plugin code that may initiate the event.
+ *
+ * @param string $type Event type
+ * @param array $args Optional enumerated array of event arguments
+ *
+ * @return void
+ */
+ protected function assertDoesNotEmitEvent($type, array $args = array())
+ {
+ // Ugly hack to get around an issue in PHPUnit
+ // @link http://github.com/sebastianbergmann/phpunit-mock-objects/issues/issue/5#issue/5/comment/343524
+ $callback = create_function(
+ '$plugin, $type, $args',
+ 'if (get_class($plugin) == "' . $this->pluginClass . '"
+ && $type == "' . $type . '"
+ && $args == "' . var_export($args, true) . '") {
+ trigger_error("Instance of ' . $this->pluginClass
+ . ' unexpectedly emitted event of type ' . $type
+ . '", E_USER_ERROR);
+ }'
+ );
- // Clean the event handler again... just incase this time.
- $this->handler->clearEvents();
+ $this->events
+ ->expects($this->any())
+ ->method('addEvent')
+ ->will($this->returnCallback($callback));
+ }
- return $testResult;
+ /**
+ * Modifies the plugin handler to include an expectation of a plugin
+ * being retrieved, indicating a dependency. Note that this must be
+ * called BEFORE executing the plugin code that may load that plugin
+ * dependency, which is usually located in onLoad().
+ *
+ * @param string $name Short name of the plugin required as a dependency
+ *
+ * @return void
+ */
+ public function assertRequiresPlugin($name)
+ {
+ $this->plugins
+ ->expects($this->atLeastOnce())
+ ->method('getPlugin')
+ ->with($name);
}
+ /**
+ * Creates an in-memory copy of a specified SQLite database file and
+ * returns a connection to it.
+ *
+ * @param string $path Path to the SQLite file to copy
+ *
+ * @return PDO Connection to the database copy
+ */
+ public function getMockDatabase($path)
+ {
+ $original = new PDO('sqlite:' . $path);
+ $copy = new PDO('sqlite::memory:');
+
+ $result = $original->query('SELECT sql FROM sqlite_master');
+ while ($sql = $result->fetchColumn()) {
+ $copy->exec($sql);
+ }
+
+ $tables = array();
+ $result = $original->query('SELECT name FROM sqlite_master WHERE type = "table"');
+ while ($table = $result->fetchColumn()) {
+ $tables[] = $table;
+ }
+
+ foreach ($tables as $table) {
+ $result = $original->query('SELECT * FROM ' . $table);
+ $insert = null;
+ $copy->beginTransaction();
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $columns = array_keys($row);
+ if (empty($insert)) {
+ $insert = $copy->prepare(
+ 'INSERT INTO "' . $table . '" (' .
+ '"' . implode('", "', $columns) . '"' .
+ ') VALUES (' .
+ ':' . implode(', :', $columns) .
+ ')'
+ );
+ }
+ $insert->execute($row);
+ }
+ $copy->commit();
+ unset($insert);
+ }
+
+ return $copy;
+ }
}