我怎样才能对调度事件的方法进行单元测试,这些事件会改变被测方法的行为?
How can I unit test a method that dispatches events that alter behavior of the method under test?
我有一个自定义事件 public 属性:
class MyCustomEvent
{
public $allowAction = false;
}
我有一个 class 创建此事件,并使用事件对象分派一个事件,允许事件 listeners/subscribers 更改对象上的 属性。
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
应该处理的不同结果?
您无法控制事件对象的属性,因为您是在函数内创建对象。
有几种方法可以解决这个问题。
1) 让您的 doSomething 方法将 MyCustomEvent
对象作为其参数。然后你就可以传入一个模拟对象并以这种方式控制它。
2) 不要在 doSomething
中创建事件,而是让调度程序 return 具有所需属性的 MyCustomEvent
。因此,在您的测试中,您将有一个 mockDispatcher
将 return 来自 dispatch
方法的事件对象。
3) 传入一个事件工厂对象,您可以使用该对象获取适当事件的实例。然后你可以模拟它并让它 return 成为你的模拟事件对象。
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,最后两个选项具有模拟对象的测试气味 returning 模拟对象,这并不理想。但根据周围的建筑,这可能是你要去的方向。
创建在方法中使用的对象总是一种代码味道,并且使测试变得非常困难。大多数事件处理方法都将事件作为参数。
答案:只需创建一个您将用于测试的可配置侦听器,并使其在每种情况下都按照您想要的方式运行。
不要那样做! 这里的设计风格是事件接收器不应该能够改变发出事件的方法的逻辑。如果有 2 个听众怎么办?其中一个可以设置一个值,另一个可以设置另一个值?最后一个会赢,第一个听众不知道。
事件是一种通知另一个对象的方式,而发出实体不知道谁会收听(可能没有其他对象)。关于有多少听众,发射器应该以相同的方式工作。如果您需要让其他对象控制逻辑的某些方面,请明确执行(如果有疑问,请写另一个问题,我们会尽力提供帮助)
我有一个自定义事件 public 属性:
class MyCustomEvent
{
public $allowAction = false;
}
我有一个 class 创建此事件,并使用事件对象分派一个事件,允许事件 listeners/subscribers 更改对象上的 属性。
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
应该处理的不同结果?
您无法控制事件对象的属性,因为您是在函数内创建对象。
有几种方法可以解决这个问题。
1) 让您的 doSomething 方法将 MyCustomEvent
对象作为其参数。然后你就可以传入一个模拟对象并以这种方式控制它。
2) 不要在 doSomething
中创建事件,而是让调度程序 return 具有所需属性的 MyCustomEvent
。因此,在您的测试中,您将有一个 mockDispatcher
将 return 来自 dispatch
方法的事件对象。
3) 传入一个事件工厂对象,您可以使用该对象获取适当事件的实例。然后你可以模拟它并让它 return 成为你的模拟事件对象。
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,最后两个选项具有模拟对象的测试气味 returning 模拟对象,这并不理想。但根据周围的建筑,这可能是你要去的方向。
创建在方法中使用的对象总是一种代码味道,并且使测试变得非常困难。大多数事件处理方法都将事件作为参数。
答案:只需创建一个您将用于测试的可配置侦听器,并使其在每种情况下都按照您想要的方式运行。
不要那样做! 这里的设计风格是事件接收器不应该能够改变发出事件的方法的逻辑。如果有 2 个听众怎么办?其中一个可以设置一个值,另一个可以设置另一个值?最后一个会赢,第一个听众不知道。
事件是一种通知另一个对象的方式,而发出实体不知道谁会收听(可能没有其他对象)。关于有多少听众,发射器应该以相同的方式工作。如果您需要让其他对象控制逻辑的某些方面,请明确执行(如果有疑问,请写另一个问题,我们会尽力提供帮助)