如何对一个方法进行单元测试,该方法会分派改变被测方法行为的事件


How can I unit test a method that dispatches events that alter behavior of the method under test?

我有一个自定义事件,它有一个公共属性:

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应该处理的不同结果?

答案:只需创建一个可配置的侦听器,用于测试,并使其在每种情况下都按照您想要的方式运行。


不要那样做这里的设计气味是,事件接收器不应该能够更改发出事件的方法的逻辑。如果有两个听众呢?其中一个可以设定一个值,另一个可以设置另一个值?最后一个会赢,而第一个听众并不知道这一点。

事件是一种通知另一个对象的方式,而发出实体不知道谁将侦听(可能没有其他对象)。发射器应该以相同的方式处理有多少侦听器。如果您需要让其他对象控制逻辑的某些方面,请明确执行(如果有疑问,请写另一个问题,我们将尽力提供帮助)

您无法控制事件对象的属性,因为您正在函数中创建对象。

有几种方法可以解决这个问题。

1) 让您的doSomething方法将MyCustomEvent对象作为其参数。然后,您就可以传入一个mock对象并以这种方式控制它。

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,最后两个选项具有模拟对象返回模拟对象的测试气味,这并不理想。但根据周围的建筑,这可能是你必须走的方向。

创建用于方法的对象总是一种代码味道,这会使测试变得非常困难。大多数事件处理方法都将事件作为参数。