Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php
declare(strict_types=1);
namespace Test\Feature;
use Shared\Domain\DomainEvent;
use Shared\Domain\EventSourcedAggregateRoot;
use Webmozart\Assert\Assert;
use function array_shift;
/**
* This class is designed for inheritance, and provides facilities to test a generic {@see EventSourcedAggregateRoot}.
*
* @psalm-template TestedAggregateRootType of EventSourcedAggregateRoot
*/
abstract class EventSourcedAggregateContext
{
/**
* @var DomainEvent[]
* @psalm-var list<DomainEvent>
*/
private array $pastHistory = [];
/**
* @var DomainEvent[]|null
* @psalm-var null|list<DomainEvent>
*/
private ?array $recordedEvents = null;
/** @psalm-var TestedAggregateRootType|null */
private ?EventSourcedAggregateRoot $aggregateRoot = null;
/** @AfterScenario */
final public function ensure_no_further_events_were_recorded() : void
{
Assert::isEmpty($this->recordedEvents);
}
/** @psalm-return class-string<TestedAggregateRootType> */
abstract protected function aggregateRootClassName() : string;
/** @psalm-param TestedAggregateRootType $aggregateRoot */
final protected function useManuallyCreatedAggregateRoot(EventSourcedAggregateRoot $aggregateRoot) : void
{
Assert::null(
$this->aggregateRoot,
'You are replacing a pre-existing aggregate root: why are there two aggregates in your test?'
);
$this->aggregateRoot = $aggregateRoot;
}
final protected function givenPastEvent(DomainEvent $event) : void
{
$this->pastHistory[] = $event;
}
/**
* @psalm-template ExpectedEventType of DomainEvent
* @psalm-param class-string<ExpectedEventType> $expectedEventType
* @psalm-return ExpectedEventType
*/
final protected function expectNextRecordedEvent(string $expectedEventType) : DomainEvent
{
if ($this->recordedEvents === null) {
$this->recordedEvents = $this->getOrReconstituteAggregateRoot()->popRecordedEvents();
}
$event = array_shift($this->recordedEvents);
Assert::isInstanceOf($event, $expectedEventType);
return $event;
}
/** @psalm-return TestedAggregateRootType */
final protected function getOrReconstituteAggregateRoot() : EventSourcedAggregateRoot
{
return $this->aggregateRoot
?? $this->aggregateRoot = $this->aggregateRootClassName()::reconstituteFromHistory($this->pastHistory);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment