PHP单元测试-使用mockry模拟静态自动加载类

PHP单元测试-使用mockry模拟静态自动加载类,php,unit-testing,static,composer-php,mockery,Php,Unit Testing,Static,Composer Php,Mockery,我想对一个包装器类进行单元测试,以实现正式的PHP段集成。因此,我必须用mocky进行模拟,这样就不会有任何真正的API请求 问题 要模拟的类只包含静态方法。正因为如此,我试着这样嘲笑它(用): $segment=mockry::mock('alias:segment') 这是可行的,但前提是该类不是由composer自动加载的。如果我加载它-就像我必须为应用程序的其余部分加载一样-我会得到错误 无法加载模拟段,类已存在。 (这是有道理的,因为文档声明在加载之前不得加载别名类。) 问题 我如何模

我想对一个包装器类进行单元测试,以实现正式的PHP段集成。因此,我必须用mocky进行模拟,这样就不会有任何真正的API请求

问题

要模拟的类只包含静态方法。正因为如此,我试着这样嘲笑它(用):

$segment=mockry::mock('alias:segment')

这是可行的,但前提是该类不是由composer自动加载的。如果我加载它-就像我必须为应用程序的其余部分加载一样-我会得到错误

无法加载模拟段,类已存在。

(这是有道理的,因为文档声明在加载之前不得加载别名类。)

问题


我如何模拟这个(邪恶的?)类,但仍然像往常一样在我的应用程序的其余部分中使用它?

本质上,你不能用静态调用模拟类

静态调用总是精确地引用要调用的类和方法,这相当于精确地指向要执行的文件和代码行(如果您假设基本的自动加载功能可用)

执行不同代码的唯一方法是不包含原始类,而是首先加载模拟类代码。无论您是否有其他代码文件,或者是否使用mockry调用
eval()
,这都无关紧要。两种方法都会奏效

但它们也只能工作一次。您不能在以后的测试中切换回原始代码,因为每次脚本运行只能定义一次类。这里的问题是无法切换实现(如原始版本与模拟版本与另一个模拟版本)

在评论中也提到了解决方案:不要有带有静态方法的类。始终创建类的实例并调用动态方法。通过这种方式,您可以轻松地模拟该类,但它需要首先创建一个实例,并提供一种方法将该类(或至少是模拟的类)注入到要测试的代码中

作为一种通用模式,如果项目中没有依赖注入,我将使用此模式(有时我必须处理一些遗留问题):

这样我就不必注入类,但我可以注入一个mock而不是真实的类

对于依赖项注入可用的情况,构造函数将是一个非常基本的初始值设定项:

public function __construct(MyClass $class) {
    $this->class = $class;
}

如果您的依赖注入框架能够进行自动连接(如PHP-DI),并且您只有一个
MyClass
,这将非常有用,因为这将自动注入,而无需您定义任何内容。

如果我理解正确,这听起来更像是一个设计问题,而不是“如何模拟它”的问题。引用该库的类究竟是什么样子的?您是以某种方式注入依赖项,还是直接从方法调用库?我也想过,但没有找到解决方案。目前,我直接这样调用这些方法:
Segment::track(…)
。注入类是没有意义的,因为它只有静态方法,不是吗?我认为这是非常有意义的,因为这将允许您通过测试通过模拟版本。在生产环境中,这似乎是不必要的,因为它需要为类进行额外的设置,但我认为这是使类可测试的常用方法。如果你有能力使用依赖注入容器(比如),那就不是问题了。是的,一个额外的包装器也是一个很好的解决方案,但是你仍然需要测试这个包装器并遇到同样的问题。另外,您的类已经是一个包装器了,所以我认为这只是一个包装器异常:)这样想:您想要测试的就是调用某个对象上的特定方法。实现这一点的最佳方法是控制该对象是什么,为了实现这一点,您只需从外部传入它,而不是在类中自动加载它。在生产环境中,你通过了真正的类,在测试用例中,你通过了mock.Mh,我明白了。但这意味着我将注入Segment类的一个实例,然后对其调用一个静态方法,如
$segmentInstance->track(…)
。我认为这是一种糟糕的做法,因为它“隐藏”了我在静态上下文中调用的
track(…)
?谢谢Sven!在我自己的代码中,我尽可能地使用依赖项注入,但在这种情况下,我必须模拟一个正式的包(段PHP集成)。这个包是以一种完全静态的方式编写的,所以我一直在寻找一种模拟这个类的方法——我无法更改它。但是,您可以用几种方法处理这种情况。1.您可以忽略在内部将所有调用传递给实例化对象的静态类,自己也可以这样做。2.你可以用一个不稳定的包装器来包装这个静态类,然后你可以模仿它。谢谢你,很高兴知道没有更简单的方法可以做到这一点。
public function __construct(MyClass $class) {
    $this->class = $class;
}