Quiero probar unitariamente una clase contenedora para la integración oficial de Segment PHP. Por lo tanto, tendré que burlarme de la clase Segmento con Mockery para que no haya ninguna solicitud de API real.
El problema
La clase para simular consta solo de métodos estáticos. Por eso trato de burlarme así (conalias ):
$segment = Mockery::mock('alias:Segment');
Esto funciona, pero solo si el compositor no carga automáticamente la clase. Si lo cargo, como tengo que hacer para el resto de la aplicación, obtendré el error
Could not load mock Segment, class already exists.
(Eso tiene sentido, porque los documentos establecen que una clase con alias no debe cargarse antes).
La pregunta
¿Cómo puedo burlarme de esta clase (¿malvada?), pero aún así usarla como de costumbre en el resto de mi aplicación?
Esencialmente, no puede simular clases con llamadas estáticas.
Las llamadas estáticas siempre hacen referencia exactamente a la clase y el método que se va a llamar, lo que equivale a señalar exactamente el archivo y la línea de código que se va a ejecutar (si supone que la capacidad básica de carga automática está disponible).
La única forma de ejecutar un código diferente es NO haber incluido la clase original, sino cargar primero el código de la clase simulada. No importa si tiene un archivo de código alternativo o si está llamando a eval() usando Mockery. Ambas formas funcionarán.
Pero también funcionarán solo una vez. No puede volver al código original en una prueba posterior, porque la clase solo se puede definir una vez por ejecución de script. Y el problema aquí es no poder cambiar las implementaciones (como el original frente al simulado frente a otro simulado).
La solución, que también se mencionó en los comentarios: no tenga clases con métodos estáticos. Siempre cree instancias de clases y llame a métodos dinámicos. De esta manera, puede simular fácilmente la clase, pero primero requiere crear una instancia y ofrecer una forma de inyectar la clase (o al menos la clase simulada) en el código que desea probar.
Como patrón genérico, estoy usando esto si la inyección de dependencia no está disponible en el proyecto (a veces tengo que lidiar con algunas cosas heredadas):
public function __construct(MyClass $class = null) { $this->class = $class ?: new MyClass(); }De esta manera, no tengo que inyectar la clase, pero podría inyectar un simulacro en lugar de la clase real.
Para situaciones en las que está disponible la inyección de dependencia, el constructor será un inicializador muy básico:
public function __construct(MyClass $class) { $this->class = $class; } Esto funciona muy bien si su marco de inyección de dependencia es capaz de hacer cableado automático (como PHP-DI), y solo tiene exactamente una MyClass , porque se inyectará automáticamente sin que tenga que definir nada.