Completed
Push — master ( 4eebe5...24088c )
by Constantin
02:41
created

BddAggregateTestHelper::thenShouldFailWith()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 20
cts 20
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 25
nc 16
nop 2
crap 6
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\EventNotExpected;
20
use Gica\Cqrs\Testing\Exceptions\ExpectedEventNotYielded;
21
use Gica\Cqrs\Testing\Exceptions\NoExceptionThrown;
22
use Gica\Cqrs\Testing\Exceptions\WrongEventClassYielded;
23
use Gica\Cqrs\Testing\Exceptions\WrongExceptionClassThrown;
24
use Gica\Cqrs\Testing\Exceptions\WrongExceptionMessageWasThrown;
25
use Gica\Types\Guid;
26
27
class BddAggregateTestHelper
28
{
29
    private $aggregateId;
30
31
    /** @var EventDispatcher */
32
    private $eventDispatcher;
33
34
    private $priorEvents = [];
35
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 8
    public function __construct(
50
        CommandSubscriber $commandSubscriber
51
    )
52
    {
53 8
        $this->commandSubscriber = $commandSubscriber;
54 8
        $this->eventDispatcher = new EventDispatcherBySubscriber(new ManualEventSubscriber());
55 8
        $this->eventsApplierOnAggregate = new EventsApplierOnAggregate();
56 8
        $this->commandApplier = new CommandApplier();
57
58 8
        $this->priorEvents = [];
59 8
        $this->command = null;
60 8
    }
61
62 8
    public function getCommandSubscriber(): CommandSubscriber
63
    {
64 8
        return $this->commandSubscriber;
65
    }
66
67 8
    public function onAggregate($aggregate)
68
    {
69 8
        $this->aggregate = $aggregate;
70 8
        $this->aggregateId = 123;
71 8
    }
72
73 8
    public function given(...$priorEvents)
74
    {
75 8
        $this->priorEvents = $this->decorateEventsWithMetadata($priorEvents);
76 8
    }
77
78
    /**
79
     * @param Event[] $priorEvents
80
     * @return EventWithMetaData[]
81
     */
82
    private function decorateEventsWithMetadata(array $priorEvents)
83
    {
84 8
        return array_map(function (Event $event) {
85 8
            return $this->decorateEventWithMetaData($event);
86 8
        }, $priorEvents);
87
    }
88
89 8
    public function when($command)
90
    {
91 8
        $this->command = $command;
92 8
    }
93
94 4
    public function then(...$expectedEvents)
95
    {
96 4
        $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
97
98 4
        $newEvents = $this->executeCommand($this->command);
0 ignored issues
show
Documentation introduced by
$this->command is of type null, but the function expects a object<Gica\Cqrs\Command>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
99
100 4
        $this->assertTheseEvents($expectedEvents, $newEvents);
101 1
    }
102
103 4
    public function executeCommand(Command $command)
104
    {
105 4
        $handler = $this->getCommandSubscriber()->getHandlerForCommand($command);
106
107 4
        $newEventsGenerator = $this->commandApplier->applyCommand($this->aggregate, $command, $handler->getMethodName());
108
109
        /** @var EventWithMetaData[] $eventsWithMetaData */
110 4
        $eventsWithMetaData = [];
111
112 4
        $newEvents = [];
113
114 4 View Code Duplication
        foreach ($newEventsGenerator as $event) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115 4
            $eventWithMetaData = $this->decorateEventWithMetaData($event);
116
117 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, [$eventWithMetaData]);
118
119 4
            $eventsWithMetaData[] = $eventWithMetaData;
120 4
            $newEvents[] = $event;
121
        }
122
123 4
        foreach ($eventsWithMetaData as $eventWithMetaData) {
124 4
            $this->eventDispatcher->dispatchEvent($eventWithMetaData);
125
        }
126
127 4
        return $newEvents;
128
    }
129
130 8
    private function decorateEventWithMetaData(Event $event): EventWithMetaData
131
    {
132 8
        return new EventWithMetaData($event, $this->factoryMetaData());
133
    }
134
135 4
    public function thenShouldFailWith($expectedExceptionClass, $expectedExceptionMessage = null)
136
    {
137
        try {
138 4
            $handler = $this->getCommandSubscriber()->getHandlerForCommand($this->command);
0 ignored issues
show
Documentation introduced by
$this->command is of type null, but the function expects a object<Gica\Cqrs\Command>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139
140 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
141
142 4
            iterator_to_array(
143 4
                $this->commandApplier->applyCommand(
144 4
                    $this->aggregate, $this->command, $handler->getMethodName()));
0 ignored issues
show
Documentation introduced by
$this->command is of type null, but the function expects a object<Gica\Cqrs\Command>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145
146 1
            throw new NoExceptionThrown(
147 1
                sprintf("Exception '%s' was is expected, but none was thrown", $expectedExceptionClass));
148
149 4
        } catch (\Throwable $thrownException) {
150
151 4
            if ($thrownException instanceof NoExceptionThrown) {
152 1
                throw $thrownException;//rethrown
153
            }
154
155 3
            if (!$this->isClassOrSubClass($expectedExceptionClass, $thrownException)) {
156 1
                throw new WrongExceptionClassThrown(
157
                    sprintf(
158 1
                        "Exception '%s' was expected, but '%s(%s)' was thrown",
159
                        $expectedExceptionClass,
160
                        get_class($thrownException),
161 1
                        $thrownException->getMessage()));
162
            }
163
164 2
            if ($expectedExceptionMessage && $thrownException->getMessage() != $expectedExceptionMessage) {
165 1
                throw new WrongExceptionMessageWasThrown(
166
                    sprintf(
167 1
                        "Exception with message '%s' was expected, but '%s' was thrown",
168
                        $expectedExceptionMessage,
169 1
                        $thrownException->getMessage()));
170
            }
171
172
        }
173 1
    }
174
175
176 4
    public function assertTheseEvents(array $expectedEvents, array $actualEvents)
177
    {
178 4
        $expectedEvents = array_values($expectedEvents);
179 4
        $actualEvents = array_values($actualEvents);
180
181 4 View Code Duplication
        foreach ($expectedEvents as $k => $expectedEvent) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182 4
            if (!isset($actualEvents[$k])) {
183 1
                throw new ExpectedEventNotYielded("Expected event #$k not fired (should have class " . get_class($expectedEvent) . ")");
184
            }
185
186 4
            $actualEvent = $actualEvents[$k];
187
188 4
            if ($this->hashEvent($expectedEvent) != $this->hashEvent($actualEvent)) {
189 4
                throw new WrongEventClassYielded("Wrong event #{$k} of class " . get_class($expectedEvent) . " emitted");
190
            }
191
        }
192
193 2 View Code Duplication
        foreach ($actualEvents as $k => $actualEvent) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194 2
            if (!isset($expectedEvents[$k])) {
195 1
                throw new EventNotExpected("Actual event #$k fired when it should't (should have class " . get_class($actualEvent) . ")");
196
            }
197
198 2
            $expectedEvent = $expectedEvents[$k];
199
200 2
            if ($this->hashEvent($expectedEvent) != $this->hashEvent($actualEvent)) {
201 2
                throw new WrongEventClassYielded("Wrong event #{$k} of class " . get_class($expectedEvent) . " emitted");
202
            }
203
        }
204 1
    }
205
206 4
    public function hashEvent($event)
207
    {
208 4
        return array_merge(['___class' => get_class($event)], (array)($event));
209
    }
210
211 8
    private function factoryMetaData(): MetaData
212
    {
213 8
        return new MetaData(
214 8
            $this->aggregateId, get_class($this->aggregate), new \DateTimeImmutable(), new Guid()
215
        );
216
    }
217
218 3
    private function isClassOrSubClass(string $parentClass, $childClass): bool
219
    {
220 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...
221
    }
222
}