在动态分配的实例上模拟一个方法


Mocking a method on dynamically allocated instance?

背景:我正在为一些实践开发MVC框架,并希望确保所有内容都经过100%的单元测试。

当前的设置是拥有应用程序类(Ex_App)的一个实例。主脚本要求Dispatcher/Router提供控制器名称。此控制器名称是实现Ex_Controller的类的名称。结果作为Ex_Dispatch_Result的实例返回。该结果使用invokeController($dispatchResult)函数传递给Ex_App实例。

这就是魔法发生的地方。下面的列表是摘录:

$controllerName = $dispatchResult->getControllerName();
... checks for validaty of class name ...
$controller = new $controllerName();
$controller->prepare($this);

我正在使用PHPUnit进行单元测试,并且能够模拟调度结果,正确地检查验证控制器的类名是否有效。问题是如何检查是否调用了prepare。

我想做一些类似的事情:

$mockController = $this->getMockBuilder('Ex_Controller')
  ->setMockClassName('Invoke_Correct_Controller')
  ->getMock();
$mockController->expects($this->once())->method('prepare');

然而,由于Invoke_Correct_Controller的新实例是在调用invokeController时创建的,因此它不会是这个mock,因此expects()调用完全无关。

我可以让Ex_Dispatch_Result类负责返回控制器并进行测试,但在返回实例之前,我需要验证类名的正确性,在我看来,责任应该由Ex_App类承担,而不是由"哑壳"Ex_Dispatch_Result类承担。

PHPUnit框架中是否缺少一些可以用来测试此处代码的东西,或者一些可以在我的实例中工作的有用模式?我觉得传递控制器名称比从一开始传递控制器实例要好得多,因为这需要初始化每个可能的控制器。因此,我有点想坚持传递名称,并使用Ex_App作为控制器实例的工厂。

也许我只是过度思考了这个问题的一部分,但这种情况有时会发生。这就是为什么第三方的全新外观通常有效:-)

您可以做以下几件事:

  • 提取控制器创建逻辑以分离类,例如ControllerFactory,然后模拟控制器工厂实例,使其返回$mockController
  • 提取控制器创建逻辑以分离方法并使用部分嘲讽
  • 从$dispatchResult->getControllerName()返回$mockController,这可能需要对$dispatchResult甚至其他东西进行模拟

如果您想要更详细的答案,请提供更多的类和方法的代码示例。