简体   繁体   English

如何对一个调度事件的方法进行单元测试,该事件会更改被测方法的行为?

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

I have a custom event that has a public property: 我有一个具有公共属性的自定义事件:

class MyCustomEvent
{
    public $allowAction = false;
}

I have a class that creates this event, and dispatches an event with the event object, allowing event listeners/subscribers to change the property on the object. 我有一个创建此事件的类,并与事件对象一起调度事件,从而允许事件侦听器/订阅者更改该对象的属性。

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
        }
    }
}

How can I unit test doSomething ? 如何对doSomething单元测试? I need a way to control the properties of the event object, but the event object is not a dependency I can mock. 我需要一种方法来控制事件对象的属性,但是事件对象不是我可以模拟的依赖项。 It's created within the method I'm testing. 它是在我正在测试的方法中创建的。

I don't think this is a design smell, since this is how imagine most developers dispatch events. 我不认为这是设计气味,因为这是大多数开发人员调度事件的想象方式。 What can I do here to properly test the different outcomes that doSomething should deal with? 我在这里可以做什么以正确测试doSomething应该处理的不同结果?

Answer: Just create a configurable listener that you will use for the tests, and make it behave the way you want in each case. 答:只需创建一个用于测试的可配置侦听器,并使其在每种情况下都能按照您希望的方式运行。


don't do that! 不要那样做! The design smell here is that event receivers should not be able to alter the logic of the method that emits the event. 这里的设计气味是事件接收者不应该能够更改发出事件的方法的逻辑。 What if there were 2 listeners? 如果有2位听众该怎么办? One of them can set a value and the other set another value? 其中一个可以设置一个值,另一个可以设置另一个值? The last one will win and the first listener does not know that. 最后一个将获胜,而第一个听众则不知道。

Events are a way of notifying another objects without the emitting entity knowing who will listen (maybe no other object). 事件是一种通知其他对象的方法,而发光实体不知道谁会听(也许没有其他对象)。 The emitter should work the same way regarding how many listeners there are. 对于有多少个侦听器,发射器应该以相同的方式工作。 If you need to have some other object taking control over some aspect of the logic, do it explicitly (if in doubt, write another question and we'll try to help) 如果您需要让其他对象来控制逻辑的某些方面,请明确地进行操作(如有疑问,请写另一个问题,我们将尽力提供帮助)

You aren't able to control the properties of the event object because you are creating the object within your function. 您无法控制事件对象的属性,因为您正在函数中创建对象。

There are a couple of ways to get around this. 有两种方法可以解决此问题。

1) Have your doSomething method take a MyCustomEvent object as its argument. 1)让doSomething方法将MyCustomEvent对象作为其参数。 Then you would be able to pass in a mock object and control it that way. 这样您就可以传递一个模拟对象并以这种方式对其进行控制。

2) Don't create the event in doSomething instead have the dispatcher return a MyCustomEvent with the properties that you want. 2)不要在doSomething创建事件,而是让调度程序返回具有所需属性的MyCustomEvent So in your test, you would have a mockDispatcher that would return the event object from the dispatch method. 因此,在您的测试中,您将有一个mockDispatcher ,它将从dispatch方法返回事件对象。

3) Pass in an event factory object that you can use to get instances of the proper event. 3)传递事件工厂对象,您可以使用该对象来获取适当事件的实例。 Then you can mock this and have it return a mock event object for you. 然后,您可以对此进行模拟,并使其返回模拟事件对象。

4) You can use a callback function for the dispatch method for the event dispatcher. 4)您可以将回调函数用于事件分派器的dispatch方法。 Your function can then set the MyCustomEvent::$allowAction property to what ever you want. 然后,您的函数可以将MyCustomEvent::$allowAction属性设置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, the last two options have the test smell of mock objects returning mock objects which isn't ideal. IMO,最后两个选项具有模拟对象返回非理想对象的测试气味。 But depending on the surrounding architecture, it may be the direction that you have to go. 但是,取决于周围的体系结构,这可能是您必须走的方向。

Creating objects for use in a method is always a code smell and makes testing very difficult. 创建要在方法中使用的对象总是有代码味道,并且使测试非常困难。 Most event handling methods take the event as the argument. 大多数事件处理方法都将事件作为参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM