我有一个具有公共属性的自定义事件:
class MyCustomEvent
{
public $allowAction = false;
}
我有一个创建此事件的类,并使用事件对象调度事件,允许事件侦听器/订阅者更改对象的属性。
class MyBizLogic
{
private $dispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function doSomething()
{
$event = new MyCustomEvent();
$dispatcher = $this->dispatcher->dispatch('my_custom_event', $event);
if ($event->allowAction) {
// do action
} else {
// do something else
}
}
}
我如何进行单元测试doSomething
?我需要一种方法来控制事件对象的属性,但事件对象不是我可以模拟的依赖项。它是在我正在测试的方法中创建的。
我不认为这是一种设计气味,因为这是大多数开发人员调度事件的想法。我可以在这里做些什么来正确测试doSomething
应该处理的不同结果?
答案 0 :(得分:1)
您无法控制事件对象的属性,因为您正在函数中创建对象。
有几种方法可以解决这个问题。
1)让你的doSomething方法以MyCustomEvent
对象作为参数。然后你就可以传入一个模拟对象并以这种方式控制它。
2)不要在doSomething
中创建活动,而是让调度员返回一个MyCustomEvent
,其中包含您想要的属性。因此,在您的测试中,您将拥有一个mockDispatcher
,它将从dispatch
方法返回事件对象。
3)传入一个事件工厂对象,您可以使用它来获取正确事件的实例。然后你可以模拟它并让它为你返回一个模拟事件对象。
4)您可以为事件调度程序的dispatch
方法使用回调函数。然后,您的函数可以将MyCustomEvent::$allowAction
属性设置为您想要的属性。
$allowAction = 'foo';
$mockEventDispatcher->expects($this->once())
->method('dispatch')
->with('my_custom_event', $this->isInstanceOf('MyCustomEvent')
->will($this->returnCallback(function($string, $event) use ($allowAction) {
$event->allowAction = $allowAction
// Return whatever the dispatcher is supposed to return.
}));
IMO,最后两个选项具有模拟对象的测试气味,返回模拟对象并不理想。但是根据周围的架构,它可能是你必须走的方向。
创建在方法中使用的对象总是代码味道,并且使测试非常困难。大多数事件处理方法都将事件作为参数。
答案 1 :(得分:1)
答案:只需创建一个可用于测试的可配置侦听器,并使其在每种情况下都按照您想要的方式运行。
不要这样做!这里的设计气味是事件接收器不应该改变发出事件的方法的逻辑。如果有2个听众怎么办?其中一个可以设置一个值而另一个可以设置另一个值?最后一个将赢,第一个听众不知道。
事件是一种通知另一个对象的方式,而不会让发射实体知道谁会听(也许没有其他对象)。关于有多少听众,发射器应该以相同的方式工作。如果您需要让某些其他对象控制逻辑的某些方面,请明确地执行(如果有疑问,请写另一个问题,我们会尽力帮助)