创建部分模拟
当我们只需要模拟一个对象的几个方法,而让其余方法正常响应调用(即按实现)时,部分模拟很有用。Mockery 实现了三种不同的创建部分模拟的策略。每种策略都有其特定的优缺点,因此我们使用哪种策略将取决于我们自己的偏好和需要模拟的源代码。
我们之前已经稍微谈过 部分测试替身,但我们想在这里对此主题进行一些扩展。
运行时部分测试替身
运行时部分测试替身,也称为被动部分模拟,是模拟对象的默认状态。
$mock = \Mockery::mock('MyClass')->makePartial();
对于运行时部分模拟,我们假设所有方法都将简单地委托给父类(MyClass)的原始方法,除非方法调用与已知的期望匹配。如果我们对某个特定的方法调用没有匹配的期望,该调用将被委托给正在模拟的类。由于模拟和未模拟调用之间的划分完全取决于我们定义的期望,因此无需预先定义要模拟哪些方法。
请参阅 大父类 部分的菜谱条目,以获取运行时部分测试替身的用法示例。
生成的部分测试替身
生成的部分测试替身,也称为传统部分模拟,预先定义了类的哪些方法将被模拟,哪些方法将被保留为未模拟(即可以正常调用)。创建传统模拟的语法是:
$mock = \Mockery::mock('MyClass[foo,bar]');
在上面的示例中,MyClass 的 foo() 和 bar() 方法将被模拟,但其他 MyClass 方法不受影响。我们需要为 foo() 和 bar() 方法定义期望,以指定它们的模拟行为。
别忘了我们可以传入构造函数参数,因为未模拟的方法可能依赖于这些参数!
$mock = \Mockery::mock('MyNamespace\MyClass[foo]', array($arg1, $arg2));
请参阅 构造函数参数 部分以了解相关内容。
代理部分模拟
代理部分模拟是最后的手段。我们可能会遇到一个类,由于它被标记为 final,因此根本无法被模拟。同样,我们可能会发现一个类的方法被标记为 final。在这种情况下,我们不能简单地扩展该类并重写方法进行模拟——我们需要发挥创造力。
$mock = \Mockery::mock(new MyClass);
是的,新的模拟对象是一个代理。它会拦截调用,并将其重定向到被代理的对象(我们构造并传入的对象),以便处理不受任何期望影响的方法。间接地说,这允许我们模拟标记为 final 的方法,因为代理不受这些限制。权衡是显而易见的——代理部分模拟将无法通过对被模拟类的任何类型提示检查,因为它无法扩展该类。
特殊内部情况
所有模拟对象,除了代理部分模拟之外,都允许我们使用 passthru() 期望调用来调用底层真实类的方法。这将从真实调用中返回值,并绕过任何模拟的返回队列(显然可以简单地省略)。
还有第四种部分模拟,专供内部使用。当我们尝试模拟一个包含标记为 final 的方法的类时,它会自动生成。由于我们无法重写这些方法,它们 simply 被保留为未模拟。通常,我们不需要担心这个问题,但如果我们真的必须模拟一个 final 方法,唯一可能的方法是通过代理部分模拟。例如,SplFileInfo 是一个常见的类,它会受到这种形式的自动内部部分模拟的影响,因为它包含在内部使用的公共 final 方法。