Completed
Push — master ( 3d4711...74b130 )
by Constantin
04:05
created

BddAggregateTestHelper   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 215
Duplicated Lines 3.72 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 15
dl 8
loc 215
ccs 97
cts 97
cp 1
rs 9.1666
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A getCommandSubscriber() 0 4 1
A onAggregate() 0 5 1
A given() 0 4 1
A decorateEventsWithMetadata() 0 6 1
A when() 0 4 1
A then() 0 10 1
B executeCommand() 8 26 3
A decorateEventWithMetaData() 0 4 1
B thenShouldFailWith() 0 40 6
A assertTheseEvents() 0 8 1
A checkForToFewEvents() 0 16 4
A checkForToManyEvents() 0 7 2
A hashEvent() 0 4 1
A factoryMetaData() 0 6 1
A isClassOrSubClass() 0 4 2
A checkCommand() 0 6 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/******************************************************************************
3
 * Copyright (c) 2016 Constantin Galbenu <[email protected]>             *
4
 ******************************************************************************/
5
6
namespace Gica\Cqrs\Testing;
7
8
9
use Gica\Cqrs\Command;
10
use Gica\Cqrs\Command\CommandApplier;
11
use Gica\Cqrs\Command\CommandSubscriber;
12
use Gica\Cqrs\Event;
13
use Gica\Cqrs\Event\EventDispatcher;
14
use Gica\Cqrs\Event\EventDispatcher\EventDispatcherBySubscriber;
15
use Gica\Cqrs\Event\EventsApplier\EventsApplierOnAggregate;
16
use Gica\Cqrs\Event\EventSubscriber\ManualEventSubscriber;
17
use Gica\Cqrs\Event\EventWithMetaData;
18
use Gica\Cqrs\Event\MetaData;
19
use Gica\Cqrs\Testing\Exceptions\ExpectedEventNotYielded;
20
use Gica\Cqrs\Testing\Exceptions\NoExceptionThrown;
21
use Gica\Cqrs\Testing\Exceptions\TooManyEventsFired;
22
use Gica\Cqrs\Testing\Exceptions\WrongEventClassYielded;
23
use Gica\Cqrs\Testing\Exceptions\WrongExceptionClassThrown;
24
use Gica\Cqrs\Testing\Exceptions\WrongExceptionMessageWasThrown;
25
26
class BddAggregateTestHelper
27
{
28
    private $aggregateId;
29
30
    /** @var EventDispatcher */
31
    private $eventDispatcher;
32
33
    private $priorEvents = [];
34
35
    /** @var Command */
36
    private $command;
37
    private $aggregate;
38
39
    /** @var EventsApplierOnAggregate */
40
    private $eventsApplierOnAggregate;
41
42
    /** @var CommandApplier */
43
    private $commandApplier;
44
    /**
45
     * @var CommandSubscriber
46
     */
47
    private $commandSubscriber;
48
49 9
    public function __construct(
50
        CommandSubscriber $commandSubscriber
51
    )
52
    {
53 9
        $this->commandSubscriber = $commandSubscriber;
54 9
        $this->eventDispatcher = new EventDispatcherBySubscriber(new ManualEventSubscriber());
55 9
        $this->eventsApplierOnAggregate = new EventsApplierOnAggregate();
56 9
        $this->commandApplier = new CommandApplier();
57
58 9
        $this->priorEvents = [];
59 9
        $this->command = null;
60 9
    }
61
62 8
    private function getCommandSubscriber(): CommandSubscriber
63
    {
64 8
        return $this->commandSubscriber;
65
    }
66
67 9
    public function onAggregate($aggregate)
68
    {
69 9
        $this->aggregate = $aggregate;
70 9
        $this->aggregateId = 123;
71 9
    }
72
73 9
    public function given(...$priorEvents)
74
    {
75 9
        $this->priorEvents = $this->decorateEventsWithMetadata($priorEvents);
76 9
    }
77
78
    /**
79
     * @param Event[] $priorEvents
80
     * @return EventWithMetaData[]
81
     */
82
    private function decorateEventsWithMetadata(array $priorEvents)
83
    {
84 9
        return array_map(function (Event $event) {
85 9
            return $this->decorateEventWithMetaData($event);
86 9
        }, $priorEvents);
87
    }
88
89 8
    public function when(Command $command)
90
    {
91 8
        $this->command = $command;
92 8
    }
93
94 4
    public function then(...$expectedEvents)
95
    {
96 4
        $this->checkCommand($this->command);
97
98 4
        $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
99
100 4
        $newEvents = $this->executeCommand($this->command);
101
102 4
        $this->assertTheseEvents($expectedEvents, $newEvents);
103 1
    }
104
105
    /**
106
     * @param Command $command
107
     * @return array
108
     * @throws \Exception
109
     */
110 4
    public function executeCommand(Command $command)
111
    {
112 4
        $handler = $this->getCommandSubscriber()->getHandlerForCommand($command);
113
114 4
        $newEventsGenerator = $this->commandApplier->applyCommand($this->aggregate, $command, $handler->getMethodName());
115
116
        /** @var EventWithMetaData[] $eventsWithMetaData */
117 4
        $eventsWithMetaData = [];
118
119 4
        $newEvents = [];
120
121 4 View Code Duplication
        foreach ($newEventsGenerator as $event) {
122 4
            $eventWithMetaData = $this->decorateEventWithMetaData($event);
123
124 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, [$eventWithMetaData]);
125
126 4
            $eventsWithMetaData[] = $eventWithMetaData;
127 4
            $newEvents[] = $event;
128
        }
129
130 4
        foreach ($eventsWithMetaData as $eventWithMetaData) {
131 4
            $this->eventDispatcher->dispatchEvent($eventWithMetaData);
132
        }
133
134 4
        return $newEvents;
135
    }
136
137 9
    private function decorateEventWithMetaData(Event $event): EventWithMetaData
138
    {
139 9
        return new EventWithMetaData($event, $this->factoryMetaData());
140
    }
141
142 5
    public function thenShouldFailWith($exceptionClass, $message = null)
143
    {
144 5
        $this->checkCommand($this->command);
145
146
        try {
147 4
            $handler = $this->getCommandSubscriber()->getHandlerForCommand($this->command);
148
149 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
150
151 4
            iterator_to_array(
152 4
                $this->commandApplier->applyCommand(
153 4
                    $this->aggregate, $this->command, $handler->getMethodName()));
154
155 1
            throw new NoExceptionThrown(
156 1
                sprintf("Exception '%s' was is expected, but none was thrown", $exceptionClass));
157
158 4
        } catch (\Throwable $thrownException) {
159
160 4
            if ($thrownException instanceof NoExceptionThrown) {
161 1
                throw $thrownException;//rethrown
162
            }
163
164 3
            if (!$this->isClassOrSubClass($exceptionClass, $thrownException)) {
165 1
                throw new WrongExceptionClassThrown(
166
                    sprintf(
167 1
                        "Exception '%s' was expected, but '%s(%s)' was thrown",
168
                        $exceptionClass,
169
                        get_class($thrownException),
170 1
                        $thrownException->getMessage()));
171
            }
172
173 2
            if ($message && $thrownException->getMessage() != $message) {
174 1
                throw new WrongExceptionMessageWasThrown(
175
                    sprintf(
176 1
                        "Exception with message '%s' was expected, but '%s' was thrown",
177
                        $message,
178 1
                        $thrownException->getMessage()));
179
            }
180
        }
181 1
    }
182
183 4
    public function assertTheseEvents(array $expectedEvents, array $actualEvents)
184
    {
185 4
        $expectedEvents = array_values($expectedEvents);
186 4
        $actualEvents = array_values($actualEvents);
187
188 4
        $this->checkForToFewEvents($expectedEvents, $actualEvents);
189 2
        $this->checkForToManyEvents(count($actualEvents) - count($expectedEvents));
190 1
    }
191
192 4
    private function checkForToFewEvents(array $expectedEvents, array $actualEvents)
193
    {
194 4
        foreach ($expectedEvents as $k => $expectedEvent) {
195 4
            if (!isset($actualEvents[$k])) {
196 1
                throw new ExpectedEventNotYielded(
197 1
                    "Expected event no. $k not fired (should have class: " . get_class($expectedEvent) . ")");
198
            }
199
200 4
            $actualEvent = $actualEvents[$k];
201
202 4
            if ($this->hashEvent($expectedEvent) != $this->hashEvent($actualEvent)) {
203 1
                throw new WrongEventClassYielded(
204 4
                    "Wrong event no. {$k} of class " . get_class($expectedEvent) . " emitted");
205
            }
206
        }
207 2
    }
208
209 2
    private function checkForToManyEvents(int $additionalCount)
210
    {
211 2
        if ($additionalCount > 0) {
212 1
            throw new TooManyEventsFired(
213 1
                sprintf("Additional %d events fired", $additionalCount));
214
        }
215 1
    }
216
217 4
    public function hashEvent($event)
218
    {
219 4
        return array_merge(['___class' => get_class($event)], (array)($event));
220
    }
221
222 9
    private function factoryMetaData(): MetaData
223
    {
224 9
        return new MetaData(
225 9
            $this->aggregateId, get_class($this->aggregate), new \DateTimeImmutable(), mt_rand()
226
        );
227
    }
228
229 3
    private function isClassOrSubClass(string $parentClass, $childClass): bool
230
    {
231 3
        return get_class($childClass) == $parentClass || is_subclass_of($childClass, $parentClass);
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $parentClass can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
232
    }
233
234 9
    private function checkCommand($command)
235
    {
236 9
        if (!$command instanceof Command) {
237 1
            throw new \Exception("Command is missing. Have you called method when()?");
238
        }
239
    }
240
}