Skip to content

Commit

Permalink
Implement error logging and better testing set up
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxime Rainville committed Dec 3, 2024
1 parent 38c064f commit 950ed93
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 8 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand All @@ -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'
);
}
}
```
Expand Down
7 changes: 4 additions & 3 deletions _config/events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
listenerProvider: '%$ArchiPro\EventDispatcher\ListenerProvider'
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
54 changes: 54 additions & 0 deletions src/Service/TestEventService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace ArchiPro\Silverstripe\EventDispatcher\Service;

use ArchiPro\EventDispatcher\AsyncEventDispatcher;
use ArchiPro\EventDispatcher\ListenerProvider;
use ColinODell\PsrTestLogger\TestLogger;
use SilverStripe\Core\Injector\Injector;

/**
* Extension of the AsyncEventDispatcher for testing purposes.
*
* This service will throw exceptions when errors occur to make it easier to debug issues.
*/
class TestEventService extends EventService
{
private ?TestLogger $logger = null;

public function __construct()
{
$listenerProvider = Injector::inst()->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;
}
}
61 changes: 61 additions & 0 deletions tests/php/Service/TestEventServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace ArchiPro\Silverstripe\EventDispatcher\Tests\Service;

use ArchiPro\Silverstripe\EventDispatcher\Service\TestEventService;
use Exception;
use Revolt\EventLoop;
use Revolt\EventLoop\UncaughtThrowable;
use SilverStripe\Dev\SapphireTest;

class TestEventServiceTest extends SapphireTest
{
private TestEventService $service;

protected function setUp(): void
{
parent::setUp();
$this->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();
}
}

0 comments on commit 950ed93

Please sign in to comment.