2011-11-07 15 views
15

¿Hay alguna buena manera de burlar métodos concretos en clases abstractas utilizando PHPUnit?Mocking método concreto en clase abstracta utilizando phpunit

Lo que he encontrado hasta ahora es:

  • Espera() -> será() funciona bien usando métodos abstractos
  • No funciona para los métodos concretos. El método original se ejecuta en su lugar.
  • Usando mockbuilder y dando todos los métodos abstractos y el método concreto para setMethods() funciona. Sin embargo, requiere que especifique todos los métodos abstractos, haciendo que la prueba sea frágil y demasiado detallada.
  • MockBuilder :: getMockForAbstractClass() ignora setMethod().


Estas son algunas pruebas unitarias examplifying los puntos anteriores:

abstract class AbstractClass { 
    public function concreteMethod() { 
     return $this->abstractMethod(); 
    } 

    public abstract function abstractMethod(); 
} 


class AbstractClassTest extends PHPUnit_Framework_TestCase { 
    /** 
    * This works for abstract methods. 
    */ 
    public function testAbstractMethod() { 
     $stub = $this->getMockForAbstractClass('AbstractClass'); 
     $stub->expects($this->any()) 
       ->method('abstractMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Succeeds 
    } 

    /** 
    * Ideally, I would like this to work for concrete methods too. 
    */ 
    public function testConcreteMethod() { 
     $stub = $this->getMockForAbstractClass('AbstractClass'); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL 
    } 

    /** 
    * One way to mock the concrete method, is to use the mock builder, 
    * and set the methods to mock. 
    * 
    * The downside of doing it this way, is that all abstract methods 
    * must be specified in the setMethods() call. If you add a new abstract 
    * method, all your existing unit tests will fail. 
    */ 
    public function testConcreteMethod__mockBuilder_getMock() { 
     $stub = $this->getMockBuilder('AbstractClass') 
       ->setMethods(array('concreteMethod', 'abstractMethod')) 
       ->getMock(); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Succeeds 
    } 

    /** 
    * Similar to above, but using getMockForAbstractClass(). 
    * Apparently, setMethods() is ignored by getMockForAbstractClass() 
    */ 
    public function testConcreteMethod__mockBuilder_getMockForAbstractClass() { 
     $stub = $this->getMockBuilder('AbstractClass') 
       ->setMethods(array('concreteMethod')) 
       ->getMockForAbstractClass(); 
     $stub->expects($this->any()) 
       ->method('concreteMethod') 
       ->will($this->returnValue(2)); 

     $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL 
    } 
} 
+0

No es necesario poner a prueba las clases abstractas, ya que son abstractos. ¿O también quieres escribir un testcase abstracto? – hakre

+0

La clase abstracta es una dependencia de otra clase. Entonces quiero probar SomeClass :: getMyCalculatedValue() que usa $ object-> concreteMethod(). Dado que concreteMethod() puede cambiar o puede ser difícil de configurar, quiero especificar el valor de retorno de concreteMethod(). – CheeseSucker

Respuesta

4

puedo reemplazar getMock() en mi caso de prueba de base a añadir en todos los métodos abstractos porque debe burlarse de todos modos. Podrías hacer algo similar con el constructor sin dudas.

Importante: No se puede burlar métodos privados.

public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) { 
    if ($methods !== null) { 
     $methods = array_unique(array_merge($methods, 
       self::getAbstractMethods($originalClassName, $callAutoload))); 
    } 
    return parent::getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload); 
} 

/** 
* Returns an array containing the names of the abstract methods in <code>$class</code>. 
* 
* @param string $class name of the class 
* @return array zero or more abstract methods names 
*/ 
public static function getAbstractMethods($class, $autoload=true) { 
    $methods = array(); 
    if (class_exists($class, $autoload) || interface_exists($class, $autoload)) { 
     $reflector = new ReflectionClass($class); 
     foreach ($reflector->getMethods() as $method) { 
      if ($method->isAbstract()) { 
       $methods[] = $method->getName(); 
      } 
     } 
    } 
    return $methods; 
} 
+0

Esto funciona. No necesito burlarme de métodos privados =) – CheeseSucker

+0

¿En qué se diferencia esto de 'getMockForAbstractClass'? – FoolishSeth

+1

@FoolishSeth Las versiones anteriores de PHPUnit no le permitían [especificar métodos concretos adicionales para simular] (https://github.com/sebastianbergmann/phpunit-mock-objects/pull/49). Dado que no puede instanciar un objeto simulado sin burlarse de cada método abstracto, parece razonable fusionarlos en la lista cuando se llama a 'getMock()' et al. –

Cuestiones relacionadas