From 8bf8f4c71ac23046340e29515233603bbbf4d9f4 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 12 Jan 2016 14:38:33 -0600 Subject: [PATCH 1/5] Created EventListenerIntrospectionTrait This trait is intended to provide a forwards-compatibility shim for use when testing event listener registration. Methods mimic some of the functionality already in v2 (via `getEvents()` and `getListeners()`), but in such a way as to allow common behavior when testing the two versions. In particular, it provides the assertion method `assertListenerAtPriority()`, allowing developers to test that a listener was registered for the specified event at the specified priority; this is useful when testing attachment by aggregates or of default listeners. --- src/Test/EventListenerIntrospectionTrait.php | 137 ++++++++++++ .../EventListenerIntrospectionTraitTest.php | 199 ++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 src/Test/EventListenerIntrospectionTrait.php create mode 100644 test/Test/EventListenerIntrospectionTraitTest.php diff --git a/src/Test/EventListenerIntrospectionTrait.php b/src/Test/EventListenerIntrospectionTrait.php new file mode 100644 index 0000000..5a1f100 --- /dev/null +++ b/src/Test/EventListenerIntrospectionTrait.php @@ -0,0 +1,137 @@ +getEvents(); + } + + /** + * Retrieve an interable list of listeners for an event. + * + * Given an event and an event manager, returns an iterator with the + * listeners for that event, in priority order. + * + * If $withPriority is true, the key values will be the priority at which + * the given listener is attached. + * + * Do not pass $withPriority if you want to cast the iterator to an array, + * as many listeners will likely have the same priority, and thus casting + * will collapse to the last added. + * + * @param string $event + * @param EventManager $events + * @param bool $withPriority + * @return \Traversable + */ + private function getListenersForEvent($event, EventManager $events, $withPriority = false) + { + $listeners = $events->getListeners($event); + return $this->traverseListeners($listeners, $withPriority); + } + + /** + * Assert that a given listener exists at the specified priority. + * + * @param callable $expectedListener + * @param int $expectedPriority + * @param string $event + * @param EventManager $events + * @param string $message Failure message to use, if any. + */ + private function assertListenerAtPriority( + callable $expectedListener, + $expectedPriority, + $event, + EventManager $events, + $message = '' + ) { + $message = $message ?: sprintf( + 'Listener not found for event "%s" and priority %d', + $event, + $expectedPriority + ); + $listeners = $this->getListenersForEvent($event, $events, true); + $found = false; + foreach ($listeners as $priority => $listener) { + if ($listener === $expectedListener + && $priority === $expectedPriority + ) { + $found = true; + break; + } + } + TestCase::assertTrue($found, $message); + } + + /** + * Returns an indexed array of listeners for an event. + * + * Returns an indexed array of listeners for an event, in priority order. + * Priority values will not be included; use this only for testing if + * specific listeners are present, or for a count of listeners. + * + * @param string $event + * @param EventManager $events + * @return callable[] + */ + private function getArrayOfListenersForEvent($event, EventManager $events) + { + return iterator_to_array($this->getListenersForEvent($event, $events)); + } + + /** + * Generator for traversing listeners in priority order. + * + * @param PriorityQueue $listeners + * @param bool $withPriority When true, yields priority as key. + */ + public function traverseListeners(PriorityQueue $queue, $withPriority = false) + { + foreach ($queue as $handler) { + $listener = $handler->getCallback(); + if ($withPriority) { + $priority = (int) $handler->getMetadatum('priority'); + yield $priority => $listener; + } else { + yield $listener; + } + } + } +} diff --git a/test/Test/EventListenerIntrospectionTraitTest.php b/test/Test/EventListenerIntrospectionTraitTest.php new file mode 100644 index 0000000..fa748de --- /dev/null +++ b/test/Test/EventListenerIntrospectionTraitTest.php @@ -0,0 +1,199 @@ +events = new EventManager(); + } + + public function testGetEventsFromEventManagerReturnsEventList() + { + // @codingStandardsIgnoreStart + $this->events->attach('foo', function ($e) {}); + $this->events->attach('bar', function ($e) {}); + $this->events->attach('baz', function ($e) {}); + // @codingStandardsIgnoreEnd + + $this->assertEquals(['foo', 'bar', 'baz'], $this->getEventsFromEventManager($this->events)); + } + + public function testGetListenersForEventReturnsIteratorOfListenersForEventInPriorityOrder() + { + // @codingStandardsIgnoreStart + $callback1 = function ($e) {}; + $callback2 = function ($e) {}; + $callback3 = function ($e) {}; + $callback4 = function ($e) {}; + $callback5 = function ($e) {}; + // @codingStandardsIgnoreEnd + + $this->events->attach('foo', $callback5, 1); + $this->events->attach('foo', $callback1, 2); + $this->events->attach('foo', $callback4, 3); + $this->events->attach('foo', $callback3, 4); + $this->events->attach('foo', $callback2, 5); + + $listeners = $this->getListenersForEvent('foo', $this->events); + $this->assertInstanceOf(Traversable::class, $listeners); + $listeners = iterator_to_array($listeners); + + $this->assertEquals([ + $callback5, + $callback1, + $callback4, + $callback3, + $callback2, + ], $listeners); + } + + public function testGetListenersForEventReturnsIteratorOfListenersInAttachmentOrderWhenSamePriority() + { + // @codingStandardsIgnoreStart + $callback1 = function ($e) {}; + $callback2 = function ($e) {}; + $callback3 = function ($e) {}; + $callback4 = function ($e) {}; + $callback5 = function ($e) {}; + // @codingStandardsIgnoreEnd + + $this->events->attach('foo', $callback5); + $this->events->attach('foo', $callback1); + $this->events->attach('foo', $callback4); + $this->events->attach('foo', $callback3); + $this->events->attach('foo', $callback2); + + $listeners = $this->getListenersForEvent('foo', $this->events); + $this->assertInstanceOf(Traversable::class, $listeners); + $listeners = iterator_to_array($listeners); + + $this->assertEquals([ + $callback5, + $callback1, + $callback4, + $callback3, + $callback2, + ], $listeners); + } + + public function testGetListenersForEventCanReturnPriorityKeysWhenRequested() + { + // @codingStandardsIgnoreStart + $callback1 = function ($e) {}; + $callback2 = function ($e) {}; + $callback3 = function ($e) {}; + $callback4 = function ($e) {}; + $callback5 = function ($e) {}; + // @codingStandardsIgnoreEnd + + $this->events->attach('foo', $callback5, 1); + $this->events->attach('foo', $callback1, 2); + $this->events->attach('foo', $callback4, 3); + $this->events->attach('foo', $callback3, 4); + $this->events->attach('foo', $callback2, 5); + + $listeners = $this->getListenersForEvent('foo', $this->events, true); + $this->assertInstanceOf(Traversable::class, $listeners); + $listeners = iterator_to_array($listeners); + + $this->assertEquals([ + 1 => $callback5, + 2 => $callback1, + 3 => $callback4, + 4 => $callback3, + 5 => $callback2, + ], $listeners); + } + + public function testGetArrayOfListenersForEventReturnsArrayOfListenersInPriorityOrder() + { + // @codingStandardsIgnoreStart + $callback1 = function ($e) {}; + $callback2 = function ($e) {}; + $callback3 = function ($e) {}; + $callback4 = function ($e) {}; + $callback5 = function ($e) {}; + // @codingStandardsIgnoreEnd + + $this->events->attach('foo', $callback5, 1); + $this->events->attach('foo', $callback1, 1); + $this->events->attach('foo', $callback4, 3); + $this->events->attach('foo', $callback3, 2); + $this->events->attach('foo', $callback2, 2); + + $listeners = $this->getArrayOfListenersForEvent('foo', $this->events); + $this->assertInternalType('array', $listeners); + + $this->assertEquals([ + $callback5, + $callback1, + $callback3, + $callback2, + $callback4, + ], $listeners); + } + + public function testAssertListenerAtPriorityPassesWhenListenerIsFound() + { + // @codingStandardsIgnoreStart + $callback = function ($e) {}; + // @codingStandardsIgnoreEnd + + $this->events->attach('foo', $callback, 7); + + $this->assertListenerAtPriority($callback, 7, 'foo', $this->events); + } + + public function testAssertListenerAtPriorityFailsWhenListenerIsNotFound() + { + // @codingStandardsIgnoreStart + $event = 'foo'; + $listener = function ($e) {}; + $priority = 7; + $this->events->attach($event, $listener, $priority); + + $alternate = function ($e) {}; + + $permutations = [ + 'different-listener' => ['listener' => $alternate, 'priority' => $priority, 'event' => $event], + 'different-priority' => ['listener' => $listener, 'priority' => $priority + 1, 'event' => $event], + 'different-event' => ['listener' => $listener, 'priority' => $priority, 'event' => $event . '-FOO'], + ]; + // @codingStandardsIgnoreEnd + + foreach ($permutations as $case => $arguments) { + try { + $this->assertListenerAtPriority( + $arguments['listener'], + $arguments['priority'], + $arguments['event'], + $this->events + ); + $this->fail('assertListenerAtPriority assertion had a false positive for case ' . $case); + } catch (ExpectationFailedException $e) { + $this->assertContains(sprintf( + 'Listener not found for event "%s" and priority %d', + $arguments['event'], + $arguments['priority'] + ), $e->getMessage(), sprintf('Assertion failure message was unexpected: %s', $e->getMessage())); + } + } + } +} From 8f57b255fa3a407f1c8d2211913b6cfc9f589ff1 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 12 Jan 2016 14:46:52 -0600 Subject: [PATCH 2/5] Removed unused use statement --- src/Test/EventListenerIntrospectionTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Test/EventListenerIntrospectionTrait.php b/src/Test/EventListenerIntrospectionTrait.php index 5a1f100..d7d3613 100644 --- a/src/Test/EventListenerIntrospectionTrait.php +++ b/src/Test/EventListenerIntrospectionTrait.php @@ -10,7 +10,6 @@ namespace Zend\EventManager\Test; use PHPUnit_Framework_TestCase as TestCase; -use ReflectionProperty; use Zend\EventManager\EventManager; use Zend\Stdlib\PriorityQueue; From be75ec1c3ddafaeb56f99c654a8968ec8463c041 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 12 Jan 2016 14:49:46 -0600 Subject: [PATCH 3/5] Use PHPUnit_Framework_Assert instead of TestCase --- src/Test/EventListenerIntrospectionTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Test/EventListenerIntrospectionTrait.php b/src/Test/EventListenerIntrospectionTrait.php index d7d3613..ddf1e0e 100644 --- a/src/Test/EventListenerIntrospectionTrait.php +++ b/src/Test/EventListenerIntrospectionTrait.php @@ -9,7 +9,7 @@ namespace Zend\EventManager\Test; -use PHPUnit_Framework_TestCase as TestCase; +use PHPUnit_Framework_Assert as Assert; use Zend\EventManager\EventManager; use Zend\Stdlib\PriorityQueue; @@ -96,7 +96,7 @@ private function assertListenerAtPriority( break; } } - TestCase::assertTrue($found, $message); + Assert::assertTrue($found, $message); } /** From bb065a272b40df468e0a985a65ecbf5bf2bfbead Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 12 Jan 2016 17:06:13 -0600 Subject: [PATCH 4/5] Added CHANGELOG for #19 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 277b7ab..1a1ebc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 2.6.2 - 2016-01-12 + +### Added + +- [#19](https://github.com/zendframework/zend-eventmanager/pull/19) adds a new + trait, `Zend\EventManager\Test\EventListenerIntrospectionTrait`, intended for + composition in unit tests. It provides a number of methods that can be used + to retrieve listeners with or without associated priority, and the assertion + `assertListenerAtPriority(callable $listener, $priority, $event, EventManager $events, $message = '')`, + which can be used for testing that a listener was registered at the specified + priority with the specified event. + + The features in this patch are intended to facilitate testing against both + version 2 and version 3 of zend-eventmanager, as it provides a consistent API + for retrieving lists of events and listeners between the two versions. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + ## 2.6.0 - 2015-09-29 ### Added From 504954ac4633dcbc1b9f25a9f9d6894429943185 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 12 Jan 2016 17:08:24 -0600 Subject: [PATCH 5/5] Updated copyright date --- src/Test/EventListenerIntrospectionTrait.php | 2 +- test/Test/EventListenerIntrospectionTraitTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Test/EventListenerIntrospectionTrait.php b/src/Test/EventListenerIntrospectionTrait.php index ddf1e0e..f1d7d04 100644 --- a/src/Test/EventListenerIntrospectionTrait.php +++ b/src/Test/EventListenerIntrospectionTrait.php @@ -3,7 +3,7 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zend-eventmanager for the canonical source repository - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) * @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md */ diff --git a/test/Test/EventListenerIntrospectionTraitTest.php b/test/Test/EventListenerIntrospectionTraitTest.php index fa748de..ec27fa2 100644 --- a/test/Test/EventListenerIntrospectionTraitTest.php +++ b/test/Test/EventListenerIntrospectionTraitTest.php @@ -3,7 +3,7 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zend-eventmanager for the canonical source repository - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) * @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md */