Page 1 of 1

Mocking abstract classes with SimpleTest

Posted: Sun Jun 17, 2007 7:58 am
by Chris Corbyn
Error wrote:Fatal error: Declaration of PartialSmtpConnectionIO::write() must be compatible with that of Swift_Connection::write() in /Users/d11wtq/PHPLibs/simpletest/mock_objects.php(1001) : eval()'d code on line 134
I'm trying to Mock this class:

Code: Select all

<?php

/**
 * Swift Mailer Connection Base
 * All connection handlers extend this abstract class
 * Please read the LICENSE file
 * @author Chris Corbyn <chris@w3style.co.uk>
 * @package Swift_Connection
 * @license GNU Lesser General Public License
 */

/**
 * Swift Connection Base class
 * Lists methods which are required by any connections as well as providing some common functions.
 * @package Swift_Connection
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
abstract class Swift_Connection
{
  /**
   * Any extensions the server might support
   * @var array
   */
  protected $extensions = array();
  /**
   * True if the connection is ESMTP.
   * @var boolean
   */
  protected $isESMTP = false;
  
  /**
   * Set an extension which the connection reports to support
   * @param string Extension name
   * @param array Attributes of the extension
   */
  public function setExtension($name, $options=array())
  {
    $this->extensions[$name] = $options;
  }
  /**
   * Check if a given extension has been set as available
   * @param string The name of the extension
   * @return boolean
   */
  public function hasExtension($name)
  {
    return array_key_exists($name, $this->extensions);
  }
  /**
   * Execute any needed logic after connecting and handshaking
   */
  public function postConnect(Swift $instance) {}
  /**
   * Get the list of attributes supported by the given extension
   * @param string The name of the connection
   * @return array The list of attributes
   * @throws Swift_ConnectionException If the extension cannot be found
   */
  public function getAttributes($extension)
  {
    if ($this->hasExtension($extension))
    {
      return $this->extensions[$extension];
    }
    else
    {
      throw new Swift_ConnectionException(
      "Unable to locate any attributes for the extension '" . $extension . "' since the extension cannot be found. " .
      "Consider using hasExtension() to check.");
    }
  }
  /**
   * Returns TRUE if the connection needs a EHLO greeting.
   * @return boolean
   */
  public function getRequiresEHLO()
  {
    return $this->isESMTP;
  }
  /**
   * Set TRUE if the connection needs a EHLO greeting.
   * @param boolean
   */
  public function setRequiresEHLO($set)
  {
    $this->isESMTP = (bool) $set;
  }
  /**
   * Try to start the connection
   * @throws Swift_ConnectionException If the connection cannot be started
   */
  abstract public function start();
  /**
   * Return the contents of the buffer
   * @return string
   * @throws Swift_ConnectionException If the buffer cannot be read
   */
  abstract public function read();
  /**
   * Write a command to the buffer
   * @param string The command to send
   * @throws Swift_ConnectionException If the write fails
   */
  abstract public function write($command, $end="\r\n");
  /**
   * Try to stop the connection
   * @throws Swift_ConnectionException If the connection cannot be closed/stopped
   */
  abstract public function stop();
  /**
   * Check if the connection is up or not
   * @return boolean
   */
  abstract public function isAlive();
}
I've faced this problem with this same class before. My resolution was to create an interface listing all my abstract methods, then have the abstract implement the interface, finally mocking the abstract as usual. But I'd say this is more of an issue with mock objects than a good coding practise because it's a dirty hack I'm now trying to reverse for the second time. There's no point at all listing an interface twice. Besides, PHP should still throw the same error if the interface (which is concrete) is not implemented correctly no matter how many times it's bee subclassed.

Does anyone know how I can mock this class? I need to make expectations on values passed to write() so I cannot use a partial and exclude this method neither :(

Posted: Thu Jun 21, 2007 7:58 am
by Ambush Commander
Try this patch to SimpleTest:

Open up reflection_php5.php, find the getSignature() function. Replace function contents with this:

Code: Select all

/**
         *    Gets the source code matching the declaration
         *    of a method.
         *    @param string $name    Method name.
         *    @return string         Method signature up to last
         *                           bracket.
         *    @access public
         */
        function getSignature($name) {
         // hacked up by Edward: replace most "function $name" with
         // full signature to deal with E_STRICT warnings
        	if ($name == '__set') {
        		return 'function __set($key, $value)';
        	}
        	if ($name == '__call') {
        		return 'function __call($method, $arguments)';
        	}
            if (version_compare(phpversion(), '5.1.0', '>=')) {
                if (in_array($name, array('__get', '__isset', $name == '__unset'))) {
                    return "function {$name}(\$key)";
                }
            }
            $reflector = new ReflectionMethod($this->_interface, $name);
            if (method_exists($this->_interface, $name) || 
                method_exists($this->_interface, '__call')) {
                if ($reflector->isStatic()) {
                    return "static " . $this->_getFullSignature($name);
                } else {
                	return $this->_getFullSignature($name);
                }
            }
        	//if ($this->_isInterfaceMethod($name)) {
        	    return $this->_getFullSignature($name);
        	//}
         //   return "function $name()";
        }

Posted: Thu Jun 21, 2007 8:02 am
by Jenk

Code: Select all

if (in_array($name, array('__get', '__isset', $name == '__unset'))) {
Shouldn't that be:

Code: Select all

if (in_array($name, array('__get', '__isset', '__unset'))) {

Posted: Thu Jun 21, 2007 8:05 am
by Ambush Commander
Yes. Not my fault though: that code is in the CVS. We should tell them about it.

Oh, and it looks like they fixed it (the abstract class problem) in the latest revision. Regular mocks will still emit E_STRICT errors though...

Posted: Thu Jun 21, 2007 10:03 am
by Chris Corbyn
Ambush Commander wrote:Yes. Not my fault though: that code is in the CVS. We should tell them about it.

Oh, and it looks like they fixed it (the abstract class problem) in the latest revision. Regular mocks will still emit E_STRICT errors though...
Awesome, I'll switch to the CVS head :) Won't be able to test until later however.

Posted: Thu Jun 21, 2007 10:12 am
by Ambush Commander
Word of warning: the CVS head is occasionally a bit unstable. As of right now, it seems alright though.