[TASK] Make base test classes always available
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Tests / BaseTestCase.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\StringUtility;
18
19 /**
20 * The mother of all test cases.
21 *
22 * Don't sub class this test case but rather choose a more specialized base test case,
23 * such as UnitTestCase or FunctionalTestCase
24 *
25 */
26 abstract class BaseTestCase extends \PHPUnit_Framework_TestCase
27 {
28 /**
29 * Whether global variables should be backed up
30 *
31 * @var bool
32 */
33 protected $backupGlobals = true;
34
35 /**
36 * Whether static attributes should be backed up
37 *
38 * @var bool
39 */
40 protected $backupStaticAttributes = false;
41
42 /**
43 * Creates a mock object which allows for calling protected methods and access of protected properties.
44 *
45 * @param string $originalClassName name of class to create the mock object of, must not be empty
46 * @param string[]|null $methods name of the methods to mock, null for "mock no methods"
47 * @param array $arguments arguments to pass to constructor
48 * @param string $mockClassName the class name to use for the mock class
49 * @param bool $callOriginalConstructor whether to call the constructor
50 * @param bool $callOriginalClone whether to call the __clone method
51 * @param bool $callAutoload whether to call any autoload function
52 *
53 * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
54 * a mock of $originalClassName with access methods added
55 *
56 * @throws \InvalidArgumentException
57 */
58 protected function getAccessibleMock(
59 $originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '',
60 $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true
61 ) {
62 if ($originalClassName === '') {
63 throw new \InvalidArgumentException('$originalClassName must not be empty.', 1334701880);
64 }
65
66 return $this->getMock(
67 $this->buildAccessibleProxy($originalClassName),
68 $methods,
69 $arguments,
70 $mockClassName,
71 $callOriginalConstructor,
72 $callOriginalClone,
73 $callAutoload
74 );
75 }
76
77 /**
78 * Returns a mock object which allows for calling protected methods and access
79 * of protected properties. Concrete methods to mock can be specified with
80 * the last parameter
81 *
82 * @param string $originalClassName Full qualified name of the original class
83 * @param array $arguments
84 * @param string $mockClassName
85 * @param bool $callOriginalConstructor
86 * @param bool $callOriginalClone
87 * @param bool $callAutoload
88 * @param array $mockedMethods
89 *
90 * @throws \InvalidArgumentException
91 *
92 * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
93 *
94 */
95 protected function getAccessibleMockForAbstractClass(
96 $originalClassName, array $arguments = array(), $mockClassName = '',
97 $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array()
98 ) {
99 if ($originalClassName === '') {
100 throw new \InvalidArgumentException('$originalClassName must not be empty.', 1384268260);
101 }
102
103 return $this->getMockForAbstractClass(
104 $this->buildAccessibleProxy($originalClassName),
105 $arguments,
106 $mockClassName,
107 $callOriginalConstructor,
108 $callOriginalClone,
109 $callAutoload,
110 $mockedMethods
111 );
112 }
113
114 /**
115 * Creates a proxy class of the specified class which allows
116 * for calling even protected methods and access of protected properties.
117 *
118 * @param string $className Name of class to make available, must not be empty
119 * @return string Fully qualified name of the built class, will not be empty
120 */
121 protected function buildAccessibleProxy($className)
122 {
123 $accessibleClassName = $this->getUniqueId('Tx_Phpunit_AccessibleProxy');
124 $class = new \ReflectionClass($className);
125 $abstractModifier = $class->isAbstract() ? 'abstract ' : '';
126
127 eval(
128 $abstractModifier . 'class ' . $accessibleClassName .
129 ' extends ' . $className . ' implements ' . \TYPO3\CMS\Core\Tests\AccessibleObjectInterface::class . ' {' .
130 'public function _call($methodName) {' .
131 'if ($methodName === \'\') {' .
132 'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334663993);' .
133 '}' .
134 '$args = func_get_args();' .
135 'return call_user_func_array(array($this, $methodName), array_slice($args, 1));' .
136 '}' .
137 'public function _callRef(' .
138 '$methodName, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL, &$arg4 = NULL, &$arg5= NULL, &$arg6 = NULL, ' .
139 '&$arg7 = NULL, &$arg8 = NULL, &$arg9 = NULL' .
140 ') {' .
141 'if ($methodName === \'\') {' .
142 'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334664210);' .
143 '}' .
144 'switch (func_num_args()) {' .
145 'case 0:' .
146 'throw new RuntimeException(\'The case of 0 arguments is not supposed to happen.\', 1334703124);' .
147 'break;' .
148 'case 1:' .
149 '$returnValue = $this->$methodName();' .
150 'break;' .
151 'case 2:' .
152 '$returnValue = $this->$methodName($arg1);' .
153 'break;' .
154 'case 3:' .
155 '$returnValue = $this->$methodName($arg1, $arg2);' .
156 'break;' .
157 'case 4:' .
158 '$returnValue = $this->$methodName($arg1, $arg2, $arg3);' .
159 'break;' .
160 'case 5:' .
161 '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4);' .
162 'break;' .
163 'case 6:' .
164 '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5);' .
165 'break;' .
166 'case 7:' .
167 '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);' .
168 'break;' .
169 'case 8:' .
170 '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7);' .
171 'break;' .
172 'case 9:' .
173 '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8);' .
174 'break;' .
175 'case 10:' .
176 '$returnValue = $this->$methodName(' .
177 '$arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9' .
178 ');' .
179 'break;' .
180 'default:' .
181 'throw new \InvalidArgumentException(' .
182 '\'_callRef currently only allows calls to methods with no more than 9 parameters.\'' .
183 ');' .
184 '}' .
185 'return $returnValue;' .
186 '}' .
187 'public function _set($propertyName, $value) {' .
188 'if ($propertyName === \'\') {' .
189 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664355);' .
190 '}' .
191 '$this->$propertyName = $value;' .
192 '}' .
193 'public function _setRef($propertyName, &$value) {' .
194 'if ($propertyName === \'\') {' .
195 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664545);' .
196 '}' .
197 '$this->$propertyName = $value;' .
198 '}' .
199 'public function _setStatic($propertyName, $value) {' .
200 'if ($propertyName === \'\') {' .
201 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242602);' .
202 '}' .
203 'self::$$propertyName = $value;' .
204 '}' .
205 'public function _get($propertyName) {' .
206 'if ($propertyName === \'\') {' .
207 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664967);' .
208 '}' .
209 'return $this->$propertyName;' .
210 '}' .
211 'public function _getStatic($propertyName) {' .
212 'if ($propertyName === \'\') {' .
213 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242603);' .
214 '}' .
215 'return self::$$propertyName;' .
216 '}' .
217 '}'
218 );
219
220 return $accessibleClassName;
221 }
222
223 /**
224 * Helper function to call protected or private methods
225 *
226 * @param object $object The object to be invoked
227 * @param string $name the name of the method to call
228 * @return mixed
229 */
230 protected function callInaccessibleMethod($object, $name)
231 {
232 // Remove first two arguments ($object and $name)
233 $arguments = func_get_args();
234 array_splice($arguments, 0, 2);
235
236 $reflectionObject = new \ReflectionObject($object);
237 $reflectionMethod = $reflectionObject->getMethod($name);
238 $reflectionMethod->setAccessible(true);
239 return $reflectionMethod->invokeArgs($object, $arguments);
240 }
241
242 /**
243 * Injects $dependency into property $name of $target
244 *
245 * This is a convenience method for setting a protected or private property in
246 * a test subject for the purpose of injecting a dependency.
247 *
248 * @param object $target The instance which needs the dependency
249 * @param string $name Name of the property to be injected
250 * @param mixed $dependency The dependency to inject – usually an object but can also be any other type
251 * @return void
252 * @throws \RuntimeException
253 * @throws \InvalidArgumentException
254 */
255 protected function inject($target, $name, $dependency)
256 {
257 if (!is_object($target)) {
258 throw new \InvalidArgumentException('Wrong type for argument $target, must be object.');
259 }
260
261 $objectReflection = new \ReflectionObject($target);
262 $methodNamePart = strtoupper($name[0]) . substr($name, 1);
263 if ($objectReflection->hasMethod('set' . $methodNamePart)) {
264 $methodName = 'set' . $methodNamePart;
265 $target->$methodName($dependency);
266 } elseif ($objectReflection->hasMethod('inject' . $methodNamePart)) {
267 $methodName = 'inject' . $methodNamePart;
268 $target->$methodName($dependency);
269 } elseif ($objectReflection->hasProperty($name)) {
270 $property = $objectReflection->getProperty($name);
271 $property->setAccessible(true);
272 $property->setValue($target, $dependency);
273 } else {
274 throw new \RuntimeException('Could not inject ' . $name . ' into object of type ' . get_class($target));
275 }
276 }
277
278 /**
279 * Create and return a unique id optionally prepended by a given string
280 *
281 * This function is used because on windows and in cygwin environments uniqid() has a resolution of one second which
282 * results in identical ids if simply uniqid('Foo'); is called.
283 *
284 * @param string $prefix
285 * @return string
286 */
287 protected function getUniqueId($prefix = '')
288 {
289 return $prefix . StringUtility::getUniqueId(mt_rand());
290 }
291 }