mock - Simula un método privado con PHPUnit
mockery php (9)
Aquí hay una variación de las otras respuestas que se pueden usar para hacer que esas llamadas sean una sola línea:
public function callPrivateMethod($object, $methodName)
{
$reflectionClass = new /ReflectionClass($object);
$reflectionMethod = $reflectionClass->getMethod($methodName);
$reflectionMethod->setAccessible(true);
$params = array_slice(func_get_args(), 2); //get all the parameters after $methodName
return $reflectionMethod->invokeArgs($object, $params);
}
Tengo una pregunta sobre el uso de PHPUnit para simular un método privado dentro de una clase. Permíteme presentarte con un ejemplo:
class A {
public function b() {
// some code
$this->c();
// some more code
}
private function c(){
// some code
}
}
¿Cómo puedo rescindir el resultado del método privado para probar el código más parte de la función pública?
Resuelto parcialmente leyendo here
Por lo general, simplemente no prueba ni se burla de los métodos privados y protegidos.
Lo que quiere probar es la API pública de su clase. Todo lo demás es un detalle de implementación para su clase y no debe "romper" sus pruebas si lo cambia.
Eso también lo ayuda cuando se da cuenta de que "no puede obtener una cobertura de código del 100%" porque puede tener un código en su clase que no puede ejecutar llamando a la API pública.
Usualmente no quieres hacer esto
Pero si tu clase se ve así:
class a {
public function b() {
return 5 + $this->c();
}
private function c() {
return mt_rand(1,3);
}
}
Puedo ver la necesidad de querer simular c () ya que la función "aleatoria" es el estado global y no puedes probar eso.
La solución "clean? / Verbose? / Overcomplicated-maybe? / I-like-it-usually"
class a {
public function __construct(RandomGenerator $foo) {
$this->foo = $foo;
}
public function b() {
return 5 + $this->c();
}
private function c() {
return $this->foo->rand(1,3);
}
}
ahora ya no hay necesidad de burlarse de "c ()", ya que no contiene ningún elemento global y se puede probar muy bien.
Si no desea eliminar o no puede eliminar el estado global de su función privada (lo malo es que la mala realidad o la definición de mal puede ser diferente) puede probar contra el simulacro.
// maybe set the function protected for this to work
$testMe = $this->getMock("a", array("c"));
$testMe->expects($this->once())->method("c")->will($this->returnValue(123123));
y ejecute sus pruebas contra este simulacro ya que la única función que saca / simula es "c ()".
Para citar el libro "Pragmatic Unit Testing":
"En general, no desea romper ninguna encapsulación por el bien de la prueba (o como solía decir mamá," ¡no exponga sus partes privadas! "). La mayoría de las veces, debería poder probar una clase mediante el ejercicio de sus métodos públicos. Si hay una funcionalidad importante que se oculta detrás del acceso privado o protegido, eso podría ser una señal de advertencia de que hay otra clase que lucha por salir ".
Algunos más: por Why you don''t want test private methods.
Puede probar métodos privados, pero no puede simular (simula) la ejecución de estos métodos.
Además, la reflexión no le permite convertir un método privado en un método protegido o público. setAccessible solo le permite invocar el método original.
Alternativamente, podría usar runkit para cambiar el nombre de los métodos privados e incluir una "nueva implementación". Sin embargo, estas características son experimentales y no se recomienda su uso.
Puede obtener el método protegido falso, de modo que si puede convertir C en protegido, este código lo ayudará.
$mock = $this->getMockBuilder(''A'')
->disableOriginalConstructor()
->setMethods(array(''C''))
->getMock();
$response = $mock->B();
Esto definitivamente funcionará, funcionó para mí. Luego, para cubrir el método protegido C, puede usar clases de reflexión.
Puede usar reflection y setAccessible()
en sus pruebas para permitirle establecer el estado interno de su objeto de tal forma que devuelva lo que desea del método privado. Necesitarás estar en PHP 5.3.2.
$fixture = new MyClass(...);
$reflector = new ReflectionProperty(''MyClass'', ''myPrivateProperty'');
$reflector->setAccessible(true);
$reflector->setValue($fixture, ''value'');
// test $fixture ...
Se me ocurrió esta clase de propósito general para mi caso:
/**
* @author Torge Kummerow
*/
class Liberator {
private $originalObject;
private $class;
public function __construct($originalObject) {
$this->originalObject = $originalObject;
$this->class = new ReflectionClass($originalObject);
}
public function __get($name) {
$property = $this->class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($this->originalObject);
}
public function __set($name, $value) {
$property = $this->class->getProperty($name);
$property->setAccessible(true);
$property->setValue($this->originalObject, $value);
}
public function __call($name, $args) {
$method = $this->class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($this->originalObject, $args);
}
}
Con esta clase, ahora puede liberar de manera fácil y transparente todas las funciones / campos privados de cualquier objeto.
$myObject = new Liberator(new MyObject());
/* @var $myObject MyObject */ //Usefull for code completion in some IDEs
//Writing to a private field
$myObject->somePrivateField = "testData";
//Reading a private field
echo $myObject->somePrivateField;
//calling a private function
$result = $myObject->somePrivateFunction($arg1, $arg2);
Si el rendimiento es importante, se puede mejorar almacenando en caché las propiedades / métodos llamados en la clase Libertador.
Suponiendo que necesita probar $ myClass-> privateMethodX ($ arg1, $ arg2), puede hacer esto con la reflexión:
$class = new ReflectionClass ($myClass);
$method = $class->getMethod (''privateMethodX'');
$method->setAccessible(true);
$output = $method->invoke ($myClass, $arg1, $arg2);
Una opción sería hacer c()
protected
lugar de private
y luego subclase y anular c()
. Luego prueba con tu subclase. Otra opción sería refactorizar c()
en una clase diferente que pueda inyectar en A
(esto se llama inyección de dependencia). Y luego inyecte una instancia de prueba con una implementación simulada de c()
en su prueba unitaria.
Una solución alternativa es cambiar su método privado a un método protegido y luego simular.
$myMockObject = $this->getMockBuilder(''MyMockClass'')
->setMethods(array(''__construct''))
->setConstructorArgs(array("someValue", 5))
->setMethods(array(''myProtectedMethod''))
->getMock();
$response = $myMockObject->myPublicMethod();
donde myPublicMethod
llama myProtectedMethod
. Lamentablemente no podemos hacer esto con métodos privados, ya que setMethods
no puede encontrar un método privado, ya que puede encontrar un método protegido