Completed
Push — master ( 1f78f7...072297 )
by Frank
02:49
created

src/AggregateRootTestCase.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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);
0 ignored issues
show
The call to AggregateRootTestCase::handle() has too many arguments starting with $arguments.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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 3
    protected function pointInTime(): PointInTime
186
    {
187 3
        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
}