Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 46 additions & 37 deletions src/Test/AggregateRootTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ abstract class AggregateRootTestCase extends TestCase

/** @var array<mixed> */
private array $parameters = [];
private object|null $aggregate = null;

/** @var array<object|Closure> */
private array $thenExpectations = [];

private Throwable|null $thrownException = null;
/** @var class-string<Throwable>|null */
private string|null $expectedException = null;
private string|null $expectedExceptionMessage = null;
Expand All @@ -41,6 +43,7 @@ abstract protected function aggregateClass(): string;

final public function given(object ...$events): self
{
$this->aggregate = null;
$this->givenEvents = $events;

return $this;
Expand All @@ -52,6 +55,42 @@ final public function when(object $callable, mixed ...$parameters): self
$this->when = $callable;
$this->parameters = $parameters;

if ($this->givenEvents) {
$this->aggregate = $this->aggregateClass()::createFromEvents($this->givenEvents);
}

try {
$callableOrCommand = $this->when;
$return = null;

if ($callableOrCommand instanceof Closure) {
/** @var AggregateRoot|null $return */
$return = $callableOrCommand($this->aggregate);
} else {
foreach (HandlerFinder::findInClass($this->aggregateClass()) as $handler) {
if (!$callableOrCommand instanceof $handler->commandClass) {
continue;
}

$reflection = new ReflectionClass($this->aggregateClass());
$reflectionMethod = $reflection->getMethod($handler->method);

/** @var AggregateRoot|null $return */
$return = $reflectionMethod->invokeArgs($handler->static ? null : $this->aggregate, [$callableOrCommand, ...$this->parameters]);
}
}

if ($this->aggregate !== null && $return instanceof AggregateRoot) {
throw new AggregateAlreadySet();
}

if ($this->aggregate === null) {
$this->aggregate = $return;
}
} catch (Throwable $throwable) {
$this->thrownException = $throwable;
}

return $this;
}

Expand Down Expand Up @@ -89,60 +128,28 @@ final public function assert(): self
throw new NoWhenProvided();
}

$aggregate = null;

if ($this->givenEvents) {
$aggregate = $this->aggregateClass()::createFromEvents($this->givenEvents);
}

try {
$callableOrCommand = $this->when;
$return = null;

if ($callableOrCommand instanceof Closure) {
$return = $callableOrCommand($aggregate);
} else {
foreach (HandlerFinder::findInClass($this->aggregateClass()) as $handler) {
if (!$callableOrCommand instanceof $handler->commandClass) {
continue;
}

$reflection = new ReflectionClass($this->aggregateClass());
$reflectionMethod = $reflection->getMethod($handler->method);

$return = $reflectionMethod->invokeArgs($handler->static ? null : $aggregate, [$callableOrCommand, ...$this->parameters]);
}
}

if ($aggregate !== null && $return instanceof AggregateRoot) {
throw new AggregateAlreadySet();
}

if ($aggregate === null) {
$aggregate = $return;
}
} catch (Throwable $throwable) {
$this->handleException($throwable);
if ($this->thrownException !== null) {
$this->handleException($this->thrownException);

return $this;
}

$this->expectedExceptionWasNotThrown();

if (!$aggregate instanceof AggregateRoot) {
if (!$this->aggregate instanceof AggregateRoot) {
throw new NoAggregateCreated();
}

$expectedEvents = array_values(array_filter($this->thenExpectations, static fn (object $item) => !$item instanceof Closure));
$expectationCallbacks = array_filter($this->thenExpectations, static fn (object $item) => $item instanceof Closure);

$events = $aggregate->releaseEvents();
$events = $this->aggregate->releaseEvents();

self::assertEquals($expectedEvents, $events, 'The events doesn\'t match the expected events.');

foreach ($expectationCallbacks as $callback) {
try {
$callback($aggregate);
$callback($this->aggregate);
} catch (Throwable $t) {
$reflection = new ReflectionFunction($callback);

Expand All @@ -169,6 +176,8 @@ final public function reset(): void
$this->thenExpectations = [];
$this->expectedException = null;
$this->expectedExceptionMessage = null;
$this->aggregate = null;
$this->thrownException = null;
}

private function handleException(Throwable $throwable): void
Expand Down
10 changes: 10 additions & 0 deletions tests/Unit/Fixture/Notifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture;

interface Notifier
{
public function notify(): void;
}
6 changes: 5 additions & 1 deletion tests/Unit/Fixture/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ public function emitsNoEvents(): void
}

#[Handle]
public static function createProfile(CreateProfile $createProfile): self
public static function createProfile(CreateProfile $createProfile, Notifier|null $notifier = null): self
{
$self = new self();
$self->recordThat(new ProfileCreated($createProfile->id, $createProfile->email));

if ($notifier) {
$notifier->notify();
}

return $self;
}

Expand Down
41 changes: 17 additions & 24 deletions tests/Unit/Test/AggregateRootTestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\CreateProfile;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\CreateProfileWithFailure;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\Email;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\Notifier;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\Profile;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\ProfileCreated;
use Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture\ProfileError;
Expand Down Expand Up @@ -232,29 +233,6 @@ public function testVisited(): void
self::assertSame(1, $test::getCount());
}

public function testVisitedDoubleAssert(): void
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a weird test which aimed for infeciton 100%, not needed anymore.

{
$test = $this->getTester();

$test
->given(
new ProfileCreated(
ProfileId::fromString('1'),
Email::fromString('hq@patchlevel.de'),
),
)
->when(
static fn (Profile $profile) => $profile->visitProfile(new VisitProfile(ProfileId::fromString('2'))),
)
->then(
new ProfileVisited(ProfileId::fromString('2')),
);

$test->assert();
$test->assert();
self::assertSame(2, $test::getCount());
}

public function testCreation(): void
{
$test = $this->getTester();
Expand Down Expand Up @@ -310,7 +288,7 @@ public function testNoGivenAndNoCreation(): void

$test
->when(
static fn () => 'no aggregate as return',
static fn () => null,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes more sense

)
->then(
new ProfileVisited(ProfileId::fromString('2')),
Expand Down Expand Up @@ -344,6 +322,21 @@ public function testDoubleAggregateCreation(): void
self::assertSame(1, $test::getCount());
}

public function testCreationWithMock(): void
{
$test = $this->getTester();

$notifier = $this->createMock(Notifier::class);
$notifier->expects($this->once())->method('notify');
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without these changes, phpunit would always say that the function was never called.


$test
->when(new CreateProfile(ProfileId::fromString('1'), Email::fromString('hq@patchlevel.de')), $notifier)
->then(new ProfileCreated(ProfileId::fromString('1'), Email::fromString('hq@patchlevel.de')));

$test->assert();
self::assertSame(1, $test::getCount());
}

public function testReset(): void
{
$test = $this->getTester();
Expand Down
Loading