Passed
Branch merging-leagues-tournaments (a75688)
by Benedikt
07:29
created

UnitTestCase::getPartialMockForTrait()

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 9
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
declare(strict_types=1);
3
/**
4
 * Created by PhpStorm.
5
 * User: benedikt
6
 * Date: 1/6/18
7
 * Time: 7:08 PM
8
 */
9
10
namespace Tfboe\FmLib\TestHelpers;
11
12
use ArrayAccess;
13
use Doctrine\ORM\AbstractQuery;
14
use Doctrine\ORM\EntityManager;
15
use Doctrine\ORM\QueryBuilder;
16
use PHPUnit\Framework\MockObject\MockObject;
17
use PHPUnit\Framework\MockObject\Stub;
18
use PHPUnit\Framework\TestCase;
19
use ReflectionClass;
20
use ReflectionException;
21
use Tfboe\FmLib\Entity\Helpers\BaseEntity;
22
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyEntity;
23
use function class_exists;
24
25
/**
26
 * Class UnitTestCase
27
 * @package Tfboe\FmLib\TestHelpers
28
 */
29
abstract class UnitTestCase extends TestCase
30
{
31
  use ReflectionMethods;
32
  use OnlyTestLogging;
33
34
//<editor-fold desc="Public Methods">
35
36
  /**
37
   * @param array|ArrayAccess $subset
38
   * @param array|ArrayAccess $array
39
   */
40
  public static function assertArrayIsSubset($subset, $array): void
41
  {
42
    foreach ($subset as $key => $value) {
43
      self::assertArrayHasKey($key, $array);
44
      self::assertEquals($value, $array[$key]);
45
    }
46
  }
47
48
  /**
49
   * @inheritDoc
50
   */
51
  public function tearDown(): void
52
  {
53
    parent::tearDown();
54
  }
55
//</editor-fold desc="Public Methods">
56
57
//<editor-fold desc="Protected Final Methods">
58
59
  /**
60
   * Gets a mock class (with full implementation). The given arguments are used for the arguments for the constructor.
61
   * If too less arguments are given mocks are created for the rest of the constructor arguments.
62
   * @param string $className the class to mock
63
   * @param array $arguments the arguments to use for the constructor
64
   * @param string[] $mockedMethods the methods to mock in the class
65
   * @return MockObject the mocked object
66
   * @throws ReflectionException
67
   */
68
  final protected function getMockWithMockedArguments(string $className, array $arguments = [],
69
                                                      array $mockedMethods = []): MockObject
70
  {
71
72
    $reflection = new ReflectionClass($className);
73
    $params = $reflection->getConstructor()->getParameters();
74
    $allArguments = $arguments;
75
    for ($i = count($arguments), $c = count($params); $i < $c; $i++) {
76
      $allArguments[] = $this->createMock($params[$i]->getClass()->name);
77
    }
78
    return $this->getMockForAbstractClass($className, $allArguments, '', true, true, true, $mockedMethods);
79
  }
80
81
  /**
82
   * Gets a new instance of the given class. The given arguments are used for the arguments for the constructor.
83
   * If too less arguments are given mocks are created for the rest of the constructor arguments.
84
   * @param string $className the class for which to create an instance
85
   * @param array $arguments the arguments to use for the constructor
86
   * @return mixed an instance of the given class
87
   * @throws ReflectionException
88
   */
89
  final protected function getObjectWithMockedArguments($className, array $arguments = [])
90
  {
91
92
    $reflection = new ReflectionClass($className);
93
    $params = $reflection->getConstructor()->getParameters();
94
    $allArguments = $arguments;
95
    for ($i = count($arguments), $c = count($params); $i < $c; $i++) {
96
      $allArguments[$i] = $this->createMock($params[$i]->getClass()->name);
97
    }
98
    return new $className(...$allArguments);
99
  }
100
//</editor-fold desc="Protected Final Methods">
101
102
//<editor-fold desc="Protected Methods">
103
104
  /**
105
   * @param string|string[] $originalClassName
106
   * @param array $methods
107
   * @return MockObject
108
   */
109
  protected function createPartialMock($originalClassName, array $methods): MockObject
110
  {
111
    return parent::createPartialMock($originalClassName, $methods); // TODO: Change the autogenerated stub
112
  }
113
114
  /**
115
   * Creates a stub with a given set of stubbed methods, which will return the given results
116
   * @param string $class the class name
117
   * @param array $methodResults a dictionary mapping method names to results of this methods
118
   * @return MockObject|mixed the configured stub
119
   */
120
  protected function getStub(string $class, array $methodResults = []): Stub
121
  {
122
    return $this->createConfiguredMock($class, $methodResults);
123
  }
124
125
  /**
126
   * Creates an empty mock with a getId method
127
   * @param string $class the class to mock
128
   * @param string $entityId the id to assign
129
   * @param string $getterMethod the name of the getter method
130
   * @return MockObject|mixed the mocked instance
131
   */
132
  protected function createStubWithId(string $class, $entityId = "entity-id", $getterMethod = 'getId')
133
  {
134
    return $this->getStub($class, [$getterMethod => $entityId]);
135
  }
136
137
  /**
138
   * Gets a mock for an entity manager which creates a query builder which will return a query which will return the
139
   * given result.
140
   * @param array $results the result arrays the queries should return
141
   * @param string[] $expectedQueries the expected queries if set
142
   * @param string[] $otherMockedMethods list of other methods to mock
143
   * @param bool $withSetLockMode
144
   * @return MockObject|EntityManager the mocked entity manager
145
   */
146
  protected function getEntityManagerMockForQueries(array $results, array $expectedQueries = [],
147
                                                    array $otherMockedMethods = [],
148
                                                    bool $withSetLockMode = false): MockObject
149
  {
150
    $entityManager = $this->getMockForAbstractClass(EntityManager::class, [], '',
151
      false, true, true, array_merge($otherMockedMethods, ['createQueryBuilder']));
152
    assert($expectedQueries == [] || count($results) === count($expectedQueries));
153
    $entityManager->expects(static::exactly(count($results)))->method('createQueryBuilder')->willReturnCallback(
154
      function () use ($entityManager, &$results, &$expectedQueries, $withSetLockMode) {
155
        $queryBuilder = $this->getMockForAbstractClass(QueryBuilder::class, [$entityManager],
156
          '', true, true, true, ['getQuery']);
157
        /** @var MockObject|AbstractQuery $query */
158
        $query = $this->getMockBuilder(AbstractQuery::class)
159
          ->disableOriginalConstructor()
160
          ->disableOriginalClone()
161
          ->disableArgumentCloning()
162
          ->disallowMockingUnknownTypes()
163
          ->onlyMethods(['getSQL', '_doExecute', 'getResult', 'getOneOrNullResult'])
164
          ->getMock();
165
        $query->expects(static::once())->method('getResult')->willReturn(array_shift($results));
166
        $query->expects(static::atMost(1))->method('getOneOrNullResult')->willReturnCallback(
167
          function () use ($query) {
168
            $res = $query->getResult();
169
            if (count($res) === 0) {
170
              return null;
171
            } else {
172
              reset($res);
173
              return $res[key($res)];
174
            }
175
          }
176
        );
177
        if ($withSetLockMode) {
178
          $query = new class($query) {
179
            private $query;
180
181
            /**
182
             *  constructor.
183
             * @param AbstractQuery $query
184
             */
185
            public function __construct(AbstractQuery $query)
186
            {
187
              $this->query = $query;
188
            }
189
190
            /**
191
             * @return AbstractQuery
192
             */
193
            public function setLockMode()
194
            {
195
              return $this->query;
196
            }
197
          };
198
        }
199
        if ($expectedQueries !== []) {
200
          $queryBuilder->expects(static::once())->method('getQuery')->willReturnCallback(
201
            function () use ($queryBuilder, $query, &$expectedQueries) {
202
              /** @var QueryBuilder $queryBuilder */
203
              self::assertEquals(array_shift($expectedQueries), $queryBuilder->getDQL());
204
              return $query;
205
            });
206
        } else {
207
          $queryBuilder->expects(static::once())->method('getQuery')->willReturn($query);
208
        }
209
        return $queryBuilder;
210
      }
211
    );
212
    return $entityManager;
213
  }
214
215
  /**
216
   * Gets a mock for an entity manager which creates a query builder which will return a query which will return the
217
   * given result.
218
   * @param array $result the result array the query should return
219
   * @param string|null $expectedQuery the expected query if set
220
   * @param string[] $otherMockedMethods list of other methods to mock
221
   * @param int $amount the number of times this query gets sent
222
   * @param bool $withSetLockMode
223
   * @return MockObject|EntityManager the mocked entity manager
224
   */
225
  protected function getEntityManagerMockForQuery(array $result, ?string $expectedQuery = null,
226
                                                  array $otherMockedMethods = [], $amount = 1,
227
                                                  bool $withSetLockMode = false): MockObject
228
  {
229
    return $this->getEntityManagerMockForQueries(array_fill(0, $amount, $result),
230
      $expectedQuery === null ? [] : array_fill(0, $amount, $expectedQuery), $otherMockedMethods, $withSetLockMode);
231
  }
232
233
  /**
234
   * @param string $className
235
   * @param array $methodNames
236
   * @param array $additionalInterfaces
237
   * @param string|null $baseClass
238
   * @param bool $callParentConstructor
239
   * @param bool $hasInit
240
   * @return MockObject
241
   * @noinspection PhpTooManyParametersInspection
242
   */
243
  protected function getMockedEntity(string $className, array $methodNames = [], array $additionalInterfaces = [],
244
                                     ?string $baseClass = BaseEntity::class,
245
                                     bool $callParentConstructor = false,
246
                                     bool $hasInit = true): MockObject
247
  {
248
    $dynNamespace = "Dynamic\\Generated";
249
    $dynClassName = $dynNamespace . "\\" . $className;
250
    if (!class_exists($dynClassName, false)) {
251
      $base = $baseClass === null ? "" : "extends \\$baseClass ";
252
      $namespace = "\\Tfboe\\FmLib\\Entity\\";
253
      $additionalInterfaces[] = $namespace . $className . "Interface";
254
      $interfaces = implode(", \\", $additionalInterfaces);
255
      $parentConstructor = $callParentConstructor ? "parent::__construct();" : "";
256
      $init = $hasInit ? "\$this->init();" : "";
257
      $class = <<<CLASS
258
namespace $dynNamespace;
259
class $className ${base}implements $interfaces
260
{
261
  use ${namespace}Traits\\${className};
262
  public function __construct()
263
  {
264
    $parentConstructor
265
    $init
266
  }
267
}
268
CLASS;
269
      eval($class);
270
    }
271
    return $this->getMockForAbstractClass($dynClassName, [], '', true, true, true, $methodNames);
272
  }
273
274
  /**
275
   * @param string $className
276
   * @param array $methods
277
   * @param array $additionalInterfaces
278
   * @return MockObject
279
   */
280
  protected function getStubbedTournamentHierarchyEntity(string $className, array $methods = [],
281
                                                         array $additionalInterfaces = [])
282
  {
283
    return $this->getStubbedEntity($className, $methods, $additionalInterfaces, TournamentHierarchyEntity::class, true);
284
  }
285
286
  /**
287
   * @param $traitName
288
   * @param array $methods
289
   * @param array $arguments
290
   * @param string $mockClassName
291
   * @param bool $callOriginalConstructor
292
   * @param bool $callOriginalClone
293
   * @param bool $callAutoload
294
   * @param bool $cloneArguments
295
   * @return MockObject
296
   * @noinspection PhpTooManyParametersInspection
297
   */
298
  protected function getPartialMockForTrait($traitName, array $methods, array $arguments = [],
299
                                            $mockClassName = '', $callOriginalConstructor = true,
300
                                            $callOriginalClone = true, $callAutoload = true,
301
                                            $cloneArguments = false): MockObject
302
  {
303
    $o = $this->getMockForTrait($traitName, $arguments, $mockClassName, $callOriginalConstructor,
304
      $callOriginalClone, $callAutoload, array_keys($methods), $cloneArguments);
305
    $this->stubMethods($o, $methods);
306
    return $o;
307
  }
308
309
  /**
310
   * @param string $className
311
   * @param array $methods
312
   * @param array $additionalInterfaces
313
   * @param string|null $baseClass
314
   * @param bool $callParentConstructor
315
   * @param bool $hasInit
316
   * @return MockObject
317
   * @noinspection PhpTooManyParametersInspection
318
   */
319
  protected function getStubbedEntity(string $className, array $methods = [], array $additionalInterfaces = [],
320
                                      ?string $baseClass = BaseEntity::class,
321
                                      bool $callParentConstructor = false,
322
                                      bool $hasInit = true): MockObject
323
  {
324
    $o = $this->getMockedEntity($className, array_keys($methods), $additionalInterfaces, $baseClass,
325
      $callParentConstructor, $hasInit);
326
    $this->stubMethods($o, $methods);
327
    return $o;
328
  }
329
330
  /**
331
   * @param MockObject $o
332
   * @param $methods
333
   */
334
  protected function stubMethods(MockObject $o, $methods)
335
  {
336
    foreach ($methods as $method => $return) {
337
      $o->method($method)->willReturn($return);
338
    }
339
  }
340
//</editor-fold desc="Protected Methods">
341
}