【问题标题】:How can I unit test a method that dispatches events that alter behavior of the method under test?如何对调度改变被测方法行为的事件的方法进行单元测试?
【发布时间】:2015-12-04 20:11:50
【问题描述】:

我有一个具有公共属性的自定义事件:

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

【问题讨论】:

    标签: php unit-testing phpunit drupal-8


    【解决方案1】:

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


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

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

    【讨论】:

    • 我不同意这一点。我已经看到许多事件实现就是这样做的。请参阅 Symfony 中的此示例:symfony.com/doc/current/cookbook/form/…。事件侦听器修改与分派事件关联的表单对象。让事件监听器修改事件信息是很常见的。
    • 在 symfony 示例中,监听器对表单所做的更改不是表单工作所必需的。发射器不知道是否有任何其他对象正在侦听并动态添加其他字段。如果任何其他侦听器需要进行相同的更改,它会注意到该字段已添加并且不会执行任何操作。无论如何,我的错将答案与可能是评论的建议混为一谈。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 虽然我同意在方法中没有直接实例化的便利性,但我认为在这种特殊情况下问题与此无关。看到该属性应该由外部对象更改。所以事件总是会以同样的方式创建。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多