Passed
Push — master ( 5bc030...0523fc )
by Aidyn
47s queued 10s
created

EventSauceTestCase::then()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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