Skip to content

Commit a0dd26d

Browse files
committed
qa: provide tests for ErrorEmittingDispatcher
Extracted common tests between the EventDispatcher and ErrorEmittingDispatcher to the trait CommonDispatcherTests. Adds tests for additional behaviors of ErrorEmittingDispatcher. In the process, discovered a bug with the logic in `handleCaughtThrowable()`, due to a typo in an `instanceof` check.
1 parent 5e9e07d commit a0dd26d

File tree

4 files changed

+184
-48
lines changed

4 files changed

+184
-48
lines changed

src/ErrorEmittingDispatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function dispatch(object $event)
6464
*/
6565
private function handleCaughtThrowable(Throwable $e, object $event, callable $listener) : void
6666
{
67-
if ($event instanceof EventError) {
67+
if ($event instanceof ErrorEvent) {
6868
// Re-throw the original exception, per the spec.
6969
throw $event->getThrowable();
7070
}

test/CommonDispatcherTests.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
/**
3+
* @see https://github.com/phly/phly-event-dispatcher for the canonical source repository
4+
* @copyright Copyright (c) 2019 Matthew Weier O'Phinney (https:/mwop.net)
5+
* @license https://github.com/phly/phly-event-dispatcher/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace PhlyTest\EventDispatcher;
11+
12+
use Prophecy\Prophecy\ObjectProphecy;
13+
use Psr\EventDispatcher\EventDispatcherInterface;
14+
use Psr\EventDispatcher\ListenerProviderInterface;
15+
use Psr\EventDispatcher\StoppableEventInterface;
16+
17+
trait CommonDispatcherTests
18+
{
19+
abstract public function getDispatcher() : EventDispatcherInterface;
20+
abstract public function getListenerProvider() : ObjectProphecy;
21+
22+
public function testImplementsEventDispatcherInterface()
23+
{
24+
$this->assertInstanceOf(EventDispatcherInterface::class, $this->getDispatcher());
25+
}
26+
27+
public function testDispatchNotifiesAllRelevantListenersAndReturnsEventWhenNoErrorsAreRaised()
28+
{
29+
$spy = (object) ['caught' => 0];
30+
31+
$listeners = [];
32+
for ($i = 0; $i < 5; $i += 1) {
33+
$listeners[] = function (object $event) use ($spy) {
34+
$spy->caught += 1;
35+
};
36+
}
37+
38+
$event = new TestAsset\TestEvent();
39+
40+
$this->getListenerProvider()
41+
->getListenersForEvent($event)
42+
->willReturn($listeners)
43+
->shouldBeCalledOnce();
44+
45+
$dispatcher = $this->getDispatcher();
46+
47+
$this->assertSame($event, $dispatcher->dispatch($event));
48+
$this->assertSame(5, $spy->caught);
49+
}
50+
51+
public function testReturnsEventVerbatimWithoutPullingListenersIfPropagationIsStopped()
52+
{
53+
$event = $this->prophesize(StoppableEventInterface::class);
54+
$event
55+
->isPropagationStopped()
56+
->willReturn(true);
57+
58+
$dispatcher = $this->getDispatcher();
59+
$this->assertSame($event->reveal(), $dispatcher->dispatch($event->reveal()));
60+
61+
$this->getListenerProvider()
62+
->getListenersForEvent($event->reveal())
63+
->shouldNotHaveBeenCalled();
64+
}
65+
66+
public function testReturnsEarlyIfAnyListenersStopsPropagation()
67+
{
68+
$spy = (object) ['caught' => 0];
69+
70+
$event = new class ($spy) implements StoppableEventInterface {
71+
private $spy;
72+
73+
public function __construct(object $spy)
74+
{
75+
$this->spy = $spy;
76+
}
77+
78+
public function isPropagationStopped() : bool
79+
{
80+
return $this->spy->caught > 3;
81+
}
82+
};
83+
84+
$listeners = [];
85+
for ($i = 0; $i < 5; $i += 1) {
86+
$listeners[] = function (object $event) use ($spy) {
87+
$spy->caught += 1;
88+
};
89+
}
90+
91+
$this->getListenerProvider()
92+
->getListenersForEvent($event)
93+
->willReturn($listeners)
94+
->shouldBeCalledOnce();
95+
96+
$dispatcher = $this->getDispatcher();
97+
98+
$this->assertSame($event, $dispatcher->dispatch($event));
99+
$this->assertSame(4, $spy->caught);
100+
}
101+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* @see https://github.com/phly/phly-event-dispatcher for the canonical source repository
4+
* @copyright Copyright (c) 2019 Matthew Weier O'Phinney (https:/mwop.net)
5+
* @license https://github.com/phly/phly-event-dispatcher/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace PhlyTest\EventDispatcher;
11+
12+
use Phly\EventDispatcher\ErrorEmittingDispatcher;
13+
use Phly\EventDispatcher\ErrorEvent;
14+
use PHPUnit\Framework\TestCase;
15+
use Prophecy\Argument;
16+
use Prophecy\Prophecy\ObjectProphecy;
17+
use Psr\EventDispatcher\EventDispatcherInterface;
18+
use Psr\EventDispatcher\ListenerProviderInterface;
19+
use Psr\EventDispatcher\StoppableEventInterface;
20+
use RuntimeException;
21+
22+
class ErrorEmittingDispatcherTest extends TestCase
23+
{
24+
use CommonDispatcherTests;
25+
26+
public function setUp()
27+
{
28+
$this->provider = $this->prophesize(ListenerProviderInterface::class);
29+
$this->dispatcher = new ErrorEmittingDispatcher($this->provider->reveal());
30+
}
31+
32+
public function getDispatcher() : EventDispatcherInterface
33+
{
34+
return $this->dispatcher;
35+
}
36+
37+
public function getListenerProvider() : ObjectProphecy
38+
{
39+
return $this->provider;
40+
}
41+
42+
public function testDispatchesErrorEventIfAListenerRaisesAnExceptionAndThenReThrows()
43+
{
44+
$event = new TestAsset\TestEvent();
45+
$exception = new RuntimeException('TRIGGERED');
46+
$errorRaisingListener = function (TestAsset\TestEvent $event) use ($exception) {
47+
throw $exception;
48+
};
49+
50+
$errorSpy = (object) ['caught' => 0];
51+
$errorListener = function (ErrorEvent $e) use ($errorSpy, $exception, $event, $errorRaisingListener) {
52+
TestCase::assertSame($event, $e->getEvent());
53+
TestCase::assertSame($errorRaisingListener, $e->getListener());
54+
TestCase::assertSame($exception, $e->getThrowable());
55+
$errorSpy->caught += 1;
56+
};
57+
58+
$this->provider
59+
->getListenersForEvent($event)
60+
->willReturn([$errorRaisingListener])
61+
->shouldBeCalledOnce();
62+
63+
$this->provider
64+
->getListenersForEvent(Argument::type(ErrorEvent::class))
65+
->willReturn([$errorListener])
66+
->shouldBeCalledOnce();
67+
68+
$this->expectException(RuntimeException::class);
69+
$this->expectExceptionMessage('TRIGGERED');
70+
$this->dispatcher->dispatch($event);
71+
}
72+
}

test/EventDispatcherTest.php

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,65 +14,28 @@
1414
use Phly\EventDispatcher\ListenerProvider\AttachableListenerProvider;
1515
use PHPUnit\Framework\Assert;
1616
use PHPUnit\Framework\TestCase;
17+
use Prophecy\Prophecy\ObjectProphecy;
1718
use Psr\EventDispatcher\EventDispatcherInterface;
1819
use Psr\EventDispatcher\ListenerProviderInterface;
1920
use Psr\EventDispatcher\StoppableEventInterface;
2021

2122
class EventDispatcherTest extends TestCase
2223
{
23-
public function testImplementsEventDispatcherInterface()
24+
use CommonDispatcherTests;
25+
26+
public function setUp()
2427
{
25-
$listeners = $this->prophesize(ListenerProviderInterface::class)->reveal();
26-
$dispatcher = new EventDispatcher($listeners);
27-
$this->assertInstanceOf(EventDispatcherInterface::class, $dispatcher);
28+
$this->provider = $this->prophesize(ListenerProviderInterface::class);
29+
$this->dispatcher = new EventDispatcher($this->provider->reveal());
2830
}
2931

30-
public function testTriggersAllListenersWithEvent()
32+
public function getDispatcher() : EventDispatcherInterface
3133
{
32-
$event = new TestAsset\TestEvent();
33-
$counter = 0;
34-
35-
$listeners = new AttachableListenerProvider();
36-
for ($i = 0; $i < 5; $i += 1) {
37-
$listeners->listen(TestAsset\TestEvent::class, function ($e) use ($event, &$counter) {
38-
Assert::assertSame($event, $e);
39-
$counter += 1;
40-
});
41-
}
42-
43-
$dispatcher = new EventDispatcher($listeners);
44-
45-
$this->assertSame($event, $dispatcher->dispatch($event));
46-
$this->assertEquals(5, $counter);
34+
return $this->dispatcher;
4735
}
4836

49-
public function testShortCircuitsIfAListenerStopsEventPropagation()
37+
public function getListenerProvider() : ObjectProphecy
5038
{
51-
$event = new class() extends TestAsset\TestEvent implements StoppableEventInterface {
52-
use StoppableEventTrait;
53-
54-
public function stopPropagation() : void
55-
{
56-
$this->stopPropagation = true;
57-
}
58-
};
59-
60-
$counter = 0;
61-
62-
$listeners = new AttachableListenerProvider();
63-
for ($i = 0; $i < 5; $i += 1) {
64-
$listeners->listen(TestAsset\TestEvent::class, function ($e) use ($event, &$counter) {
65-
Assert::assertSame($event, $e);
66-
$counter += 1;
67-
if ($counter === 3) {
68-
$e->stopPropagation();
69-
}
70-
});
71-
}
72-
73-
$dispatcher = new EventDispatcher($listeners);
74-
75-
$this->assertSame($event, $dispatcher->dispatch($event));
76-
$this->assertEquals(3, $counter);
39+
return $this->provider;
7740
}
7841
}

0 commit comments

Comments
 (0)