Completed
Branch merging-leagues-tournaments (46817d)
by Benedikt
01:48
created

UnitTestCase   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 294
rs 10
c 0
b 0
f 0
wmc 26
lcom 1
cbo 7

14 Methods

Rating   Name   Duplication   Size   Complexity  
A assertArrayIsSubset() 0 7 2
A tearDown() 0 4 1
A getMockWithMockedArguments() 0 12 2
A getObjectWithMockedArguments() 0 11 2
A createPartialMock() 0 4 1
A createStub() 0 4 1
A createStubWithId() 0 4 1
A getEntityManagerMockForQueries() 0 46 4
A getEntityManagerMockForQuery() 0 6 2
A getMockedEntity() 0 31 5
A getMockedTournamentHierarchyEntity() 0 5 1
A getPartialMockForTrait() 0 10 1
A getStubbedEntity() 0 10 1
A stubMethods() 0 6 2
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\TestCase;
18
use ReflectionClass;
19
use ReflectionException;
20
use Tfboe\FmLib\Entity\Helpers\BaseEntity;
21
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyEntity;
22
use function class_exists;
23
24
/**
25
 * Class UnitTestCase
26
 * @package Tfboe\FmLib\TestHelpers
27
 */
28
abstract class UnitTestCase extends TestCase
29
{
30
  use ReflectionMethods;
31
  use OnlyTestLogging;
32
33
//<editor-fold desc="Public Methods">
34
35
  /**
36
   * @param array|ArrayAccess $subset
37
   * @param array|ArrayAccess $array
38
   */
39
  public static function assertArrayIsSubset($subset, $array): void
40
  {
41
    foreach ($subset as $key => $value) {
42
      self::assertArrayHasKey($key, $array);
43
      self::assertEquals($value, $array[$key]);
44
    }
45
  }
46
47
  public function tearDown()
48
  {
49
    parent::tearDown();
50
  }
51
//</editor-fold desc="Public Methods">
52
53
//<editor-fold desc="Protected Final Methods">
54
55
  /** @noinspection PhpDocMissingThrowsInspection */
56
  /**
57
   * Gets a mock class (with full implementation). The given arguments are used for the arguments for the constructor.
58
   * If too less arguments are given mocks are created for the rest of the constructor arguments.
59
   * @param string $className the class to mock
60
   * @param array $arguments the arguments to use for the constructor
61
   * @param string[] $mockedMethods the methods to mock in the class
62
   * @return MockObject the mocked object
63
   * @throws ReflectionException
64
   */
65
  protected final function getMockWithMockedArguments(string $className, array $arguments = [],
66
                                                      array $mockedMethods = []): MockObject
67
  {
68
    /** @noinspection PhpUnhandledExceptionInspection */
69
    $reflection = new ReflectionClass($className);
70
    $params = $reflection->getConstructor()->getParameters();
71
    $allArguments = $arguments;
72
    for ($i = count($arguments); $i < count($params); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
73
      $allArguments[] = $this->createMock($params[$i]->getClass()->name);
74
    }
75
    return $this->getMockForAbstractClass($className, $allArguments, '', true, true, true, $mockedMethods);
76
  }
77
78
  /**
79
   * Gets a new instance of the given class. The given arguments are used for the arguments for the constructor.
80
   * If too less arguments are given mocks are created for the rest of the constructor arguments.
81
   * @param string $className the class for which to create an instance
82
   * @param array $arguments the arguments to use for the constructor
83
   * @return mixed an instance of the given class
84
   * @throws ReflectionException
85
   */
86
  protected final function getObjectWithMockedArguments($className, array $arguments = [])
87
  {
88
    /** @noinspection PhpUnhandledExceptionInspection */
89
    $reflection = new ReflectionClass($className);
90
    $params = $reflection->getConstructor()->getParameters();
91
    $allArguments = $arguments;
92
    for ($i = count($arguments); $i < count($params); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
93
      $allArguments[$i] = $this->createMock($params[$i]->getClass()->name);
94
    }
95
    return new $className(...$allArguments);
96
  }
97
//</editor-fold desc="Protected Final Methods">
98
99
//<editor-fold desc="Protected Methods">
100
101
  /**
102
   * @param string|string[] $originalClassName
103
   * @param array $methods
104
   * @return MockObject
105
   */
106
  protected function createPartialMock($originalClassName, array $methods): MockObject
107
  {
108
    return parent::createPartialMock($originalClassName, $methods); // TODO: Change the autogenerated stub
109
  }
110
111
  /**
112
   * Creates a stub with a given set of stubbed methods, which will return the given results
113
   * @param string $class the class name
114
   * @param array $methodResults a dictionary mapping method names to results of this methods
115
   * @return MockObject|mixed the configured stub
116
   */
117
  protected function createStub(string $class, array $methodResults = []): MockObject
118
  {
119
    return $this->createConfiguredMock($class, $methodResults);
120
  }
121
122
  /**
123
   * Creates an empty mock with a getId method
124
   * @param string $class the class to mock
125
   * @param string $entityId the id to assign
126
   * @param string $getterMethod the name of the getter method
127
   * @return MockObject|mixed the mocked instance
128
   */
129
  protected function createStubWithId(string $class, $entityId = "entity-id", $getterMethod = 'getId')
130
  {
131
    return $this->createStub($class, [$getterMethod => $entityId]);
132
  }
133
134
  /**
135
   * Gets a mock for an entity manager which creates a query builder which will return a query which will return the
136
   * given result.
137
   * @param array $results the result arrays the queries should return
138
   * @param string[] $expectedQueries the expected queries if set
139
   * @param string[] $otherMockedMethods list of other methods to mock
140
   * @return MockObject|EntityManager the mocked entity manager
141
   * @throws ReflectionException
142
   */
143
  protected function getEntityManagerMockForQueries(array $results, array $expectedQueries = [],
144
                                                    array $otherMockedMethods = []): MockObject
145
  {
146
    $entityManager = $this->getMockForAbstractClass(EntityManager::class, [], '',
147
      false, true, true, array_merge($otherMockedMethods, ['createQueryBuilder']));
148
    assert($expectedQueries == [] || count($results) === count($expectedQueries));
149
    $entityManager->expects(static::exactly(count($results)))->method('createQueryBuilder')->willReturnCallback(
150
      function () use ($entityManager, &$results, &$expectedQueries) {
151
        $queryBuilder = $this->getMockForAbstractClass(QueryBuilder::class, [$entityManager],
152
          '', true, true, true, ['getQuery']);
153
        /** @var MockObject|AbstractQuery $query */
154
        $query = $this->getMockBuilder(AbstractQuery::class)
155
          ->disableOriginalConstructor()
156
          ->disableOriginalClone()
157
          ->disableArgumentCloning()
158
          ->disallowMockingUnknownTypes()
159
          ->setMethods(['setLockMode', 'getSQL', '_doExecute', 'getResult', 'getOneOrNullResult'])
160
          ->getMock();
161
        $query->expects(static::once())->method('getResult')->willReturn(array_shift($results));
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit\Framework\MockObject\MockObject, but not in Doctrine\ORM\AbstractQuery.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
162
        $query->expects(static::atMost(1))->method('getOneOrNullResult')->willReturnCallback(
163
          function () use ($query) {
164
            $res = $query->getResult();
0 ignored issues
show
Bug introduced by
The method getResult does only exist in Doctrine\ORM\AbstractQuery, but not in PHPUnit\Framework\MockObject\MockObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
165
            if (count($res) === 0) {
166
              return null;
167
            } else {
168
              reset($res);
169
              return $res[key($res)];
170
            }
171
          }
172
        );
173
        $query->method('setLockMode')->willReturn($query);
174
        if ($expectedQueries !== []) {
175
          $queryBuilder->expects(static::once())->method('getQuery')->willReturnCallback(
176
            function () use ($queryBuilder, $query, &$expectedQueries) {
177
              /** @var QueryBuilder $queryBuilder */
178
              self::assertEquals(array_shift($expectedQueries), $queryBuilder->getDQL());
179
              return $query;
180
            });
181
        } else {
182
          $queryBuilder->expects(static::once())->method('getQuery')->willReturn($query);
183
        }
184
        return $queryBuilder;
185
      }
186
    );
187
    return $entityManager;
188
  }
189
190
  /**
191
   * Gets a mock for an entity manager which creates a query builder which will return a query which will return the
192
   * given result.
193
   * @param array $result the result array the query should return
194
   * @param string|null $expectedQuery the expected query if set
195
   * @param string[] $otherMockedMethods list of other methods to mock
196
   * @param int $amount the number of times this query gets sent
197
   * @return MockObject|EntityManager the mocked entity manager
198
   * @throws ReflectionException
199
   */
200
  protected function getEntityManagerMockForQuery(array $result, ?string $expectedQuery = null,
201
                                                  array $otherMockedMethods = [], $amount = 1): MockObject
202
  {
203
    return $this->getEntityManagerMockForQueries(array_fill(0, $amount, $result),
204
      $expectedQuery === null ? [] : array_fill(0, $amount, $expectedQuery), $otherMockedMethods);
205
  }
206
207
  /** @noinspection PhpTooManyParametersInspection */
208
209
  /**
210
   * @param string $className
211
   * @param array $methodNames
212
   * @param array $additionalInterfaces
213
   * @param string|null $baseClass
214
   * @param bool $callParentConstructor
215
   * @param bool $hasInit
216
   * @return MockObject
217
   * @throws ReflectionException
218
   */
219
  protected function getMockedEntity(string $className, array $methodNames = [], array $additionalInterfaces = [],
220
                                     ?string $baseClass = BaseEntity::class,
221
                                     bool $callParentConstructor = false,
222
                                     bool $hasInit = true): MockObject
223
  {
224
    $dynNamespace = "Dynamic\\Generated";
225
    $dynClassName = $dynNamespace . "\\" . $className;
226
    if (!class_exists($dynClassName, false)) {
227
      $base = $baseClass === null ? "" : "extends \\$baseClass ";
228
      $namespace = "\\Tfboe\\FmLib\\Entity\\";
229
      $additionalInterfaces[] = $namespace . $className . "Interface";
230
      $interfaces = implode(", \\", $additionalInterfaces);
231
      $parentConstructor = $callParentConstructor ? "parent::__construct();" : "";
232
      $init = $hasInit ? "\$this->init();" : "";
233
      $class = <<<PHP
234
namespace $dynNamespace;
235
class $className ${base}implements $interfaces
236
{
237
  use ${namespace}Traits\\${className};
238
  public function __construct()
239
  {
240
    $parentConstructor
241
    $init
242
  }
243
}
244
PHP;
245
      eval($class);
246
    }
247
    $o = $this->getMockForAbstractClass($dynClassName, [], '', true, true, true, $methodNames);
248
    return $o;
249
  }
250
251
  /**
252
   * @param string $className
253
   * @param array $methods
254
   * @param array $additionalInterfaces
255
   * @return MockObject
256
   * @throws ReflectionException
257
   */
258
  protected function getMockedTournamentHierarchyEntity(string $className, array $methods = [],
259
                                                        array $additionalInterfaces = [])
260
  {
261
    return $this->getStubbedEntity($className, $methods, $additionalInterfaces, TournamentHierarchyEntity::class, true);
262
  }
263
264
  /** @noinspection PhpTooManyParametersInspection */
265
  /**
266
   * @param $traitName
267
   * @param array $methods
268
   * @param array $arguments
269
   * @param string $mockClassName
270
   * @param bool $callOriginalConstructor
271
   * @param bool $callOriginalClone
272
   * @param bool $callAutoload
273
   * @param bool $cloneArguments
274
   * @return MockObject
275
   * @throws ReflectionException
276
   */
277
  protected function getPartialMockForTrait($traitName, array $methods, array $arguments = [],
278
                                            $mockClassName = '', $callOriginalConstructor = true,
279
                                            $callOriginalClone = true, $callAutoload = true,
280
                                            $cloneArguments = false): MockObject
281
  {
282
    $o = $this->getMockForTrait($traitName, $arguments, $mockClassName, $callOriginalConstructor,
283
      $callOriginalClone, $callAutoload, array_keys($methods), $cloneArguments);
284
    $this->stubMethods($o, $methods);
285
    return $o;
286
  }
287
288
  /** @noinspection PhpTooManyParametersInspection */
289
  /**
290
   * @param string $className
291
   * @param array $methods
292
   * @param array $additionalInterfaces
293
   * @param string|null $baseClass
294
   * @param bool $callParentConstructor
295
   * @param bool $hasInit
296
   * @return MockObject
297
   * @throws ReflectionException
298
   */
299
  protected function getStubbedEntity(string $className, array $methods = [], array $additionalInterfaces = [],
300
                                      ?string $baseClass = BaseEntity::class,
301
                                      bool $callParentConstructor = false,
302
                                      bool $hasInit = true): MockObject
303
  {
304
    $o = $this->getMockedEntity($className, array_keys($methods), $additionalInterfaces, $baseClass,
305
      $callParentConstructor, $hasInit);
306
    $this->stubMethods($o, $methods);
307
    return $o;
308
  }
309
310
  /**
311
   * @param MockObject $o
312
   * @param $methods
313
   */
314
  protected function stubMethods(MockObject $o, $methods)
315
  {
316
    foreach ($methods as $method => $return) {
317
      $o->method($method)->willReturn($return);
318
    }
319
  }
320
//</editor-fold desc="Protected Methods">
321
}