Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge branch 'feature/listener-introspection'
Browse files Browse the repository at this point in the history
Close #19
  • Loading branch information
weierophinney committed Jan 12, 2016
2 parents a03de81 + 504954a commit b4354f7
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 0 deletions.
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 136 additions & 0 deletions src/Test/EventListenerIntrospectionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/

namespace Zend\EventManager\Test;

use PHPUnit_Framework_Assert as Assert;
use Zend\EventManager\EventManager;
use Zend\Stdlib\PriorityQueue;

/**
* Trait providing utility methods and assertions for use in PHPUnit test cases.
*
* This trait may be composed into a test case, and provides:
*
* - methods for introspecting events and listeners
* - methods for asserting listeners are attached at a specific priority
*
* Some functionality in this trait duplicates functionality present in the
* version 2 EventManagerInterface and/or EventManager implementation, but
* abstracts that functionality for use in v3. As such, components or code
* that is testing for listener registration should use the methods in this
* trait to ensure tests are forwards-compatible between zend-eventmanager
* versions.
*/
trait EventListenerIntrospectionTrait
{
/**
* Retrieve a list of event names from an event manager.
*
* @param EventManager $events
* @return string[]
*/
private function getEventsFromEventManager(EventManager $events)
{
return $events->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;
}
}
Assert::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;
}
}
}
}
199 changes: 199 additions & 0 deletions test/Test/EventListenerIntrospectionTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/

namespace ZendTest\EventManager\Test;

use PHPUnit_Framework_ExpectationFailedException as ExpectationFailedException;
use PHPUnit_Framework_TestCase as TestCase;
use Traversable;
use Zend\EventManager\EventManager;
use Zend\EventManager\Test\EventListenerIntrospectionTrait;

class EventListenerIntrospectionTraitTest extends TestCase
{
use EventListenerIntrospectionTrait;

public function setUp()
{
$this->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()));
}
}
}
}

0 comments on commit b4354f7

Please sign in to comment.