Passed
Push — master ( dbfb37...74a6ed )
by Frank
46s queued 13s
created

AggregateRootTestCase::persistAggregateRoot()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EventSauce\EventSourcing;
6
7
use EventSauce\EventSourcing\TestUtilities\ConsumerThatSerializesMessages;
8
use EventSauce\EventSourcing\Time\Clock;
9
use EventSauce\EventSourcing\Time\TestClock;
10
use Exception;
11
use LogicException;
12
use PHPUnit\Framework\TestCase;
13
use function get_class;
14
use function method_exists;
15
use function sprintf;
16
17
/**
18
 * @method handle(...$arguments)
19
 */
20
abstract class AggregateRootTestCase extends TestCase
21
{
22
    /**
23
     * @var InMemoryMessageRepository
24
     */
25
    protected $messageRepository;
26
27
    /**
28
     * @var AggregateRootRepository
29
     */
30
    protected $repository;
31
32
    /**
33
     * @var Exception|null
34
     */
35
    private $caughtException;
36
37
    /**
38
     * @var object[]
39
     */
40
    private $expectedEvents = [];
41
42
    /**
43
     * @var Exception|null
44
     */
45
    private $theExpectedException;
46
47
    /**
48
     * @var TestClock
49
     */
50
    private $clock;
51
52
    /**
53
     * @var bool
54
     */
55
    private $assertedScenario = false;
56
57
    /**
58
     * @var AggregateRootId
59
     */
60
    protected $aggregateRootId;
61
62
    /**
63
     * @before
64
     */
65 16
    protected function setUpEventSauce(): void
66
    {
67 16
        $className = $this->aggregateRootClassName();
68 16
        $this->clock = new TestClock();
69 16
        $this->aggregateRootId = $this->newAggregateRootId();
70 16
        $this->messageRepository = new InMemoryMessageRepository();
71 16
        $dispatcher = $this->messageDispatcher();
72 16
        $decorator = $this->messageDecorator();
73 16
        $this->repository = $this->aggregateRootRepository(
74 16
            $className,
75 16
            $this->messageRepository,
76
            $dispatcher,
77
            $decorator
78
        );
79 16
        $this->expectedEvents = [];
80 16
        $this->assertedScenario = false;
81 16
        $this->theExpectedException = null;
82 16
        $this->caughtException = null;
83 16
    }
84
85 4
    protected function retrieveAggregateRoot(AggregateRootId $id): object
86
    {
87 4
        return $this->repository->retrieve($id);
88
    }
89
90 4
    protected function persistAggregateRoot(AggregateRoot $aggregateRoot): void
91
    {
92 4
        $this->repository->persist($aggregateRoot);
93 4
    }
94
95
    /**
96
     * @after
97
     */
98 16
    protected function assertScenario(): void
99
    {
100
        // @codeCoverageIgnoreStart
101
        if ($this->assertedScenario) {
102
            return;
103
        }
104
        // @codeCoverageIgnoreEnd
105
106
        try {
107 16
            $this->assertExpectedException($this->theExpectedException, $this->caughtException);
108 13
            $this->assertLastCommitEqualsEvents(...$this->expectedEvents);
109 13
            $this->messageRepository->purgeLastCommit();
110 13
        } finally {
111 16
            $this->assertedScenario = true;
112 16
            $this->theExpectedException = null;
113 16
            $this->caughtException = null;
114
        }
115 13
    }
116
117 15
    protected function aggregateRootId(): AggregateRootId
118
    {
119 15
        return $this->aggregateRootId;
120
    }
121
122
    abstract protected function newAggregateRootId(): AggregateRootId;
123
124
    abstract protected function aggregateRootClassName(): string;
125
126
    /**
127
     * @return $this
128
     */
129 5
    protected function given(object ...$events)
130
    {
131 5
        $this->repository->persistEvents($this->aggregateRootId(), count($events), ...$events);
132 5
        $this->messageRepository->purgeLastCommit();
133
134 5
        return $this;
135
    }
136
137 1
    public function on(AggregateRootId $id)
138
    {
139 1
        return new EventStager($id, $this->messageRepository, $this->repository, $this);
140
    }
141
142
    /**
143
     * @return $this
144
     */
145 14
    protected function when(...$arguments)
146
    {
147
        try {
148 14
            if ( ! method_exists($this, 'handle')) {
149 1
                throw new LogicException(sprintf('Class %s is missing a ::handle method.', get_class($this)));
150
            }
151
152 13
            $this->handle(...$arguments);
153 4
        } catch (Exception $exception) {
154 4
            $this->caughtException = $exception;
155
        }
156
157 14
        return $this;
158
    }
159
160
    /**
161
     * @return $this
162
     */
163 7
    protected function then(object ...$events)
164
    {
165 7
        $this->expectedEvents = $events;
166
167 7
        return $this;
168
    }
169
170
    /**
171
     * @return $this
172
     */
173 2
    public function expectToFail(Exception $expectedException)
174
    {
175 2
        $this->theExpectedException = $expectedException;
176
177 2
        return $this;
178
    }
179
180
    /**
181
     * @return $this
182
     */
183 3
    protected function thenNothingShouldHaveHappened()
184
    {
185 3
        $this->expectedEvents = [];
186
187 3
        return $this;
188
    }
189
190 13
    protected function assertLastCommitEqualsEvents(object ...$events): void
191
    {
192 13
        self::assertEquals($events, $this->messageRepository->lastCommit(), 'Events are not equal.');
193 13
    }
194
195 16
    private function assertExpectedException(Exception $expectedException = null, Exception $caughtException = null): void
196
    {
197 16
        if ($expectedException == $caughtException) {
198 12
            return;
199
        }
200
201
        if (
202 4
            null !== $caughtException && (
203 4
            null === $expectedException ||
204 4
            get_class($expectedException) !== get_class($caughtException))
205
        ) {
206 3
            throw $caughtException;
207
        }
208
209 1
        self::assertEquals([$expectedException], [$caughtException], '>> Exceptions are not equal.');
210 1
    }
211
212 1
    protected function pointInTime(): PointInTime
213
    {
214 1
        return $this->clock->pointInTime();
215
    }
216
217 9
    protected function clock(): Clock
218
    {
219 9
        return $this->clock;
220
    }
221
222 16
    protected function messageDispatcher(): MessageDispatcher
223
    {
224 16
        return new SynchronousMessageDispatcher(
225 16
            new ConsumerThatSerializesMessages(),
226 16
            ...$this->consumers()
227
        );
228
    }
229
230
    /**
231
     * @return Consumer[]
232
     */
233 16
    protected function consumers(): array
234
    {
235 16
        return [];
236
    }
237
238 16
    private function messageDecorator(): MessageDecorator
239
    {
240 16
        return new MessageDecoratorChain(new DefaultHeadersDecorator());
241
    }
242
243 15
    protected function aggregateRootRepository(
244
        string $className,
245
        MessageRepository $repository,
246
        MessageDispatcher $dispatcher,
247
        MessageDecorator $decorator
248
    ): AggregateRootRepository {
249 15
        return new ConstructingAggregateRootRepository(
250 15
            $className,
251
            $repository,
252
            $dispatcher,
253
            $decorator
254
        );
255
    }
256
}
257