Completed
Pull Request — master (#21)
by Frank
04:57
created

AggregateRootTestCase::given()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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