diff --git a/README.md b/README.md index 42061ce..a596325 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ Here's an example test: use Revolt\EventLoop; use SilverStripe\Dev\SapphireTest; use SilverStripe\Core\Injector\Injector; -use ArchiPro\Silverstripe\EventDispatcher\Service\EventService; +use ArchiPro\Silverstripe\EventDispatcher\Service\TestEventService; class MyEventTest extends SapphireTest { @@ -260,8 +260,9 @@ class MyEventTest extends SapphireTest // Create your test event $event = new MyCustomEvent('test message'); - // Get the event service - $service = Injector::inst()->get(EventService::class); + // Get the Test Event Service ... this will replace the default EventService with a TestEventService + // with an implementation that will log errors to help with debugging. + $service = TestEventService::bootstrap(); // Add your test listener ... or if you have already $wasCalled = false; @@ -281,6 +282,12 @@ class MyEventTest extends SapphireTest MyCustomEventListener::wasCalled(), 'Assert some side effect of the event being handled' ); + + $this->assertCount( + 0, + $service->getLogger()->records, + 'No errors were logged' + ); } } ``` diff --git a/_config/events.yml b/_config/events.yml index 6579fbe..dd53f64 100644 --- a/_config/events.yml +++ b/_config/events.yml @@ -7,17 +7,18 @@ SilverStripe\Core\Injector\Injector: # Define the listener provider ArchiPro\EventDispatcher\ListenerProvider: class: ArchiPro\EventDispatcher\ListenerProvider - + # Default event dispatcher ArchiPro\EventDispatcher\AsyncEventDispatcher: class: ArchiPro\EventDispatcher\AsyncEventDispatcher constructor: listenerProvider: '%$ArchiPro\EventDispatcher\ListenerProvider' + logger: '%$Psr\Log\LoggerInterface.errorhandler' Psr\EventDispatcher\EventDispatcherInterface: alias: '%$ArchiPro\EventDispatcher\AsyncEventDispatcher' # Bootstrap the event service ArchiPro\Silverstripe\EventDispatcher\Service\EventService: - constructor: + constructor: dispatcher: '%$ArchiPro\EventDispatcher\AsyncEventDispatcher' - listenerProvider: '%$ArchiPro\EventDispatcher\ListenerProvider' \ No newline at end of file + listenerProvider: '%$ArchiPro\EventDispatcher\ListenerProvider' diff --git a/composer.json b/composer.json index 1a898be..7f5ab3e 100644 --- a/composer.json +++ b/composer.json @@ -9,13 +9,14 @@ "silverstripe/versioned": "^1.13 || ^2.0", "psr/event-dispatcher": "^1.0", "psr/event-dispatcher-implementation": "^1.0", - "archipro/revolt-event-dispatcher": "^0.0.0" + "archipro/revolt-event-dispatcher": "^0.1.0" }, "require-dev": { "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.0", "friendsofphp/php-cs-fixer": "^3.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10", + "colinodell/psr-testlogger": "^1.3" }, "autoload": { "psr-4": { diff --git a/src/Service/TestEventService.php b/src/Service/TestEventService.php new file mode 100644 index 0000000..87ac8a5 --- /dev/null +++ b/src/Service/TestEventService.php @@ -0,0 +1,54 @@ +get(ListenerProvider::class); + + // The test logger is useful but we don't want to force people to install it in production. + if (class_exists(TestLogger::class)) { + $this->logger = new TestLogger(); + } + $dispatcher = new AsyncEventDispatcher($listenerProvider, $this->logger, AsyncEventDispatcher::THROW_ON_ERROR); + parent::__construct($dispatcher, $listenerProvider); + } + + /** + * Bootstrap the TestEventService. Will replace the default EventService with a TestEventService. + */ + public static function bootstrap(): self + { + $service = new self(); + Injector::inst()->registerService($service, AsyncEventDispatcher::class); + return $service; + } + + /** + * Return a logger that can be used to see if an array errors were thrown by the event loop. + */ + public function getLogger(): TestLogger + { + if (!$this->logger) { + throw new \RuntimeException( + 'To use the EventService\'s test logger, you must install colinodell/psr-testlogger. ' . + '`composer require --dev colinodell/psr-testlogger`' + ); + } + return $this->logger; + } +} diff --git a/tests/php/Service/TestEventServiceTest.php b/tests/php/Service/TestEventServiceTest.php new file mode 100644 index 0000000..5cf372b --- /dev/null +++ b/tests/php/Service/TestEventServiceTest.php @@ -0,0 +1,61 @@ +service = TestEventService::bootstrap(); + } + + public function testEventDispatchLogger(): void + { + // Create test event + $event = new class () {}; + + // Add test listener + $this->service->addListener(get_class($event), function ($event) { + throw new Exception('Test exception'); + }); + + // Dispatch event + $result = $this->service->dispatch($event); + + EventLoop::run(); + + $this->assertCount( + 1, + $this->service->getLogger()->records, + 'Running the event loop will cause an error to be logged' + ); + } + + public function testEventDispatchThrow(): void + { + // Create test event + $event = new class () {}; + + // Add test listener + $this->service->addListener(get_class($event), function ($event) { + throw new Exception('Test exception'); + }); + + $this->expectException( + UncaughtThrowable::class, + 'Dispatching an event with a listener that throws an exception will throw an UncaughtThrowable' + ); + + // Dispatch event + $result = $this->service->dispatch($event)->await(); + } +}