Passed
Pull Request — master (#11)
by Aidyn
12:56
created

EventSauceTestCase::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Chocofamily\LaravelEventSauce;
4
5
use EventSauce\EventSourcing\AggregateRoot;
6
use EventSauce\EventSourcing\AggregateRootId;
7
use EventSauce\EventSourcing\AggregateRootRepository;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEvent...AggregateRootRepository. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use EventSauce\EventSourcing\ConstructingAggregateRootRepository;
9
use EventSauce\EventSourcing\Consumer;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\Consumer. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use EventSauce\EventSourcing\DefaultHeadersDecorator;
11
use EventSauce\EventSourcing\EventStager;
12
use EventSauce\EventSourcing\InMemoryMessageRepository;
13
use EventSauce\EventSourcing\MessageDecorator;
14
use EventSauce\EventSourcing\MessageDecoratorChain;
15
use EventSauce\EventSourcing\MessageDispatcher;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\MessageDispatcher. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use EventSauce\EventSourcing\MessageRepository;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\MessageRepository. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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