Passed
Pull Request — master (#54)
by Frank
02:24
created

thenNothingShouldHaveHappened()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 10
    protected function setUpEventSauce()
66
    {
67 10
        $className = $this->aggregateRootClassName();
68 10
        $this->clock = new TestClock();
69 10
        $this->aggregateRootId = $this->newAggregateRootId();
70 10
        $this->messageRepository = new InMemoryMessageRepository();
71 10
        $dispatcher = $this->messageDispatcher();
72 10
        $decorator = $this->messageDecorator();
73 10
        $this->repository = $this->aggregateRootRepository(
74 10
            $className,
75 10
            $this->messageRepository,
76 10
            $dispatcher,
77 10
            $decorator
78
        );
79 10
        $this->expectedEvents = [];
80 10
        $this->assertedScenario = false;
81 10
        $this->theExpectedException = null;
82 10
        $this->caughtException = null;
83 10
    }
84
85
    /**
86
     * @after
87
     */
88 10
    protected function assertScenario()
89
    {
90
        // @codeCoverageIgnoreStart
91
        if ($this->assertedScenario) {
92
            return;
93
        }
94
        // @codeCoverageIgnoreEnd
95
96
        try {
97 10
            $this->assertExpectedException($this->theExpectedException, $this->caughtException);
98 7
            $this->assertLastCommitEqualsEvents(...$this->expectedEvents);
99 7
            $this->messageRepository->purgeLastCommit();
100 7
        } finally {
101 10
            $this->assertedScenario = true;
102 10
            $this->theExpectedException = null;
103 10
            $this->caughtException = null;
104
        }
105 7
    }
106
107 9
    protected function aggregateRootId(): AggregateRootId
108
    {
109 9
        return $this->aggregateRootId;
110
    }
111
112
    abstract protected function newAggregateRootId(): AggregateRootId;
113
114
    abstract protected function aggregateRootClassName(): string;
115
116
    /**
117
     * @return $this
118
     */
119 2
    protected function given(object ...$events)
120
    {
121 2
        $this->repository->persistEvents($this->aggregateRootId(), count($events), ...$events);
122 2
        $this->messageRepository->purgeLastCommit();
123
124 2
        return $this;
125
    }
126
127 1
    public function on(AggregateRootId $id)
128
    {
129 1
        return new EventStager($id, $this->messageRepository, $this->repository, $this);
130
    }
131
132
    /**
133
     * @return $this
134
     */
135 9
    protected function when(...$arguments)
136
    {
137
        try {
138 9
            if ( ! method_exists($this, 'handle')) {
139 1
                throw new LogicException(sprintf('Class %s is missing a ::handle method.', get_class($this)));
140
            }
141
142 8
            $this->handle(...$arguments);
143 4
        } catch (Exception $exception) {
144 4
            $this->caughtException = $exception;
145
        }
146
147 9
        return $this;
148
    }
149
150
    /**
151
     * @return $this
152
     */
153 4
    protected function then(object ...$events)
154
    {
155 4
        $this->expectedEvents = $events;
156
157 4
        return $this;
158
    }
159
160
    /**
161
     * @return $this
162
     */
163 2
    public function expectToFail(Exception $expectedException)
164
    {
165 2
        $this->theExpectedException = $expectedException;
166
167 2
        return $this;
168
    }
169
170
    /**
171
     * @return $this
172
     */
173 1
    protected function thenNothingShouldHaveHappened()
174
    {
175 1
        $this->expectedEvents = [];
176
177 1
        return $this;
178
    }
179
180 7
    protected function assertLastCommitEqualsEvents(object ...$events)
181
    {
182 7
        self::assertEquals($events, $this->messageRepository->lastCommit(), 'Events are not equal.');
183 7
    }
184
185 10
    private function assertExpectedException(Exception $expectedException = null, Exception $caughtException = null)
186
    {
187 10
        if ($expectedException == $caughtException) {
188 6
            return;
189
        }
190
191
        if (
192 4
            null !== $caughtException && (
193 4
            null === $expectedException ||
194 4
            get_class($expectedException) !== get_class($caughtException))
195
        ) {
196 3
            throw $caughtException;
197
        }
198
199 1
        self::assertEquals([$expectedException], [$caughtException], '>> Exceptions are not equal.');
200 1
    }
201
202 1
    protected function pointInTime(): PointInTime
203
    {
204 1
        return $this->clock->pointInTime();
205
    }
206
207 8
    protected function clock(): Clock
208
    {
209 8
        return $this->clock;
210
    }
211
212 10
    protected function messageDispatcher(): MessageDispatcher
213
    {
214 10
        return new SynchronousMessageDispatcher(
215 10
            new ConsumerThatSerializesMessages(),
216 10
            ...$this->consumers()
217
        );
218
    }
219
220
    /**
221
     * @return Consumer[]
222
     */
223 10
    protected function consumers(): array
224
    {
225 10
        return [];
226
    }
227
228 10
    private function messageDecorator(): MessageDecorator
229
    {
230 10
        return new MessageDecoratorChain(new DefaultHeadersDecorator());
231
    }
232
233 10
    protected function aggregateRootRepository(
234
        string $className,
235
        MessageRepository $repository,
236
        MessageDispatcher $dispatcher,
237
        MessageDecorator $decorator
238
    ): AggregateRootRepository {
239 10
        return new ConstructingAggregateRootRepository(
240 10
            $className,
241 10
            $repository,
242 10
            $dispatcher,
243 10
            $decorator
244
        );
245
    }
246
}
247