[TASK] Use real ObjectManager stubs in unit tests
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Mvc / Controller / ActionControllerTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Extbase\Tests\Unit\Mvc\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Page\PageRenderer;
19 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
20 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
21 use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
22 use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
23 use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException;
24 use TYPO3\CMS\Extbase\Mvc\Request;
25 use TYPO3\CMS\Extbase\Mvc\RequestInterface;
26 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
27 use TYPO3\CMS\Extbase\Object\ObjectManager;
28 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
29 use TYPO3\CMS\Extbase\Reflection\ReflectionService;
30 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
31 use TYPO3Fluid\Fluid\View\AbstractTemplateView;
32 use TYPO3Fluid\Fluid\View\TemplateView as FluidTemplateView;
33
34 /**
35 * Test case
36 */
37 class ActionControllerTest extends UnitTestCase
38 {
39 /**
40 * @var bool Reset singletons created by subject
41 */
42 protected $resetSingletonInstances = true;
43
44 /**
45 * @var \TYPO3\CMS\Extbase\Mvc\Controller\ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface
46 */
47 protected $actionController;
48
49 /**
50 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
51 */
52 protected $mockObjectManager;
53
54 /**
55 * @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
56 */
57 protected $mockUriBuilder;
58
59 /**
60 * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
61 */
62 protected $mockMvcPropertyMappingConfigurationService;
63
64 /**
65 * @test
66 */
67 public function resolveActionMethodNameReturnsTheCurrentActionMethodNameFromTheRequest()
68 {
69 $mockRequest = $this->createMock(Request::class);
70 $mockRequest->expects($this->once())->method('getControllerActionName')->will($this->returnValue('fooBar'));
71 /** @var \TYPO3\CMS\Extbase\Mvc\Controller\ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
72 $mockController = $this->getAccessibleMock(ActionController::class, ['fooBarAction'], [], '', false);
73 $mockController->_set('request', $mockRequest);
74 $this->assertEquals('fooBarAction', $mockController->_call('resolveActionMethodName'));
75 }
76
77 /**
78 * @test
79 */
80 public function resolveActionMethodNameThrowsAnExceptionIfTheActionDefinedInTheRequestDoesNotExist()
81 {
82 $this->expectException(NoSuchActionException::class);
83 $this->expectExceptionCode(1186669086);
84 $mockRequest = $this->createMock(Request::class);
85 $mockRequest->expects($this->once())->method('getControllerActionName')->will($this->returnValue('fooBar'));
86 /** @var \TYPO3\CMS\Extbase\Mvc\Controller\ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
87 $mockController = $this->getAccessibleMock(ActionController::class, ['otherBarAction'], [], '', false);
88 $mockController->_set('request', $mockRequest);
89 $mockController->_call('resolveActionMethodName');
90 }
91
92 /**
93 * @test
94 *
95 * @todo: make this a functional test
96 */
97 public function initializeActionMethodArgumentsRegistersArgumentsFoundInTheSignatureOfTheCurrentActionMethod(): void
98 {
99 $mockRequest = $this->createMock(Request::class);
100 $mockArguments = $this->getMockBuilder(Arguments::class)
101 ->setMethods(['addNewArgument', 'removeAll'])
102 ->getMock();
103 $mockArguments->expects($this->at(0))->method('addNewArgument')->with('stringArgument', 'string', true);
104 $mockArguments->expects($this->at(1))->method('addNewArgument')->with('integerArgument', 'integer', true);
105 $mockArguments->expects($this->at(2))->method('addNewArgument')->with('objectArgument', 'F3_Foo_Bar', true);
106 $mockController = $this->getAccessibleMock(ActionController::class, ['fooAction', 'evaluateDontValidateAnnotations'], [], '', false);
107
108 $classSchemaMethod = new ClassSchema\Method(
109 'fooAction',
110 [
111 'params' => [
112 'stringArgument' => [
113 'position' => 0,
114 'byReference' => false,
115 'array' => false,
116 'optional' => false,
117 'allowsNull' => false,
118 'type' => 'string',
119 'hasDefaultValue' => false
120 ],
121 'integerArgument' => [
122 'position' => 1,
123 'byReference' => false,
124 'array' => false,
125 'optional' => false,
126 'allowsNull' => false,
127 'type' => 'integer',
128 'hasDefaultValue' => false
129 ],
130 'objectArgument' => [
131 'position' => 2,
132 'byReference' => false,
133 'array' => false,
134 'optional' => false,
135 'allowsNull' => false,
136 'type' => 'F3_Foo_Bar',
137 'hasDefaultValue' => false
138 ]
139 ]
140 ],
141 get_class($mockController)
142 );
143
144 $classSchemaMock = $this->createMock(ClassSchema::class);
145 $classSchemaMock
146 ->expects($this->any())
147 ->method('getMethod')
148 ->with('fooAction')
149 ->willReturn($classSchemaMethod);
150
151 $mockReflectionService = $this->createMock(ReflectionService::class);
152 $mockReflectionService
153 ->expects($this->any())
154 ->method('getClassSchema')
155 ->with(get_class($mockController))
156 ->willReturn($classSchemaMock);
157 $mockController->_set('reflectionService', $mockReflectionService);
158 $mockController->_set('request', $mockRequest);
159 $mockController->_set('arguments', $mockArguments);
160 $mockController->_set('actionMethodName', 'fooAction');
161 $mockController->_call('initializeActionMethodArguments');
162 }
163
164 /**
165 * @test
166 */
167 public function initializeActionMethodArgumentsRegistersOptionalArgumentsAsSuch(): void
168 {
169 $mockRequest = $this->createMock(Request::class);
170 $mockArguments = $this->createMock(Arguments::class);
171 $mockArguments->expects($this->at(0))->method('addNewArgument')->with('arg1', 'string', true);
172 $mockArguments->expects($this->at(1))->method('addNewArgument')->with('arg2', 'array', false, [21]);
173 $mockArguments->expects($this->at(2))->method('addNewArgument')->with('arg3', 'string', false, 42);
174 $mockController = $this->getAccessibleMock(ActionController::class, ['fooAction', 'evaluateDontValidateAnnotations'], [], '', false);
175
176 $classSchemaMethod = new ClassSchema\Method(
177 'fooAction',
178 [
179 'params' => [
180 'arg1' => [
181 'position' => 0,
182 'byReference' => false,
183 'array' => false,
184 'optional' => false,
185 'allowsNull' => false,
186 'type' => 'string',
187 'hasDefaultValue' => false
188 ],
189 'arg2' => [
190 'position' => 1,
191 'byReference' => false,
192 'array' => true,
193 'optional' => true,
194 'defaultValue' => [21],
195 'allowsNull' => false,
196 'hasDefaultValue' => true
197 ],
198 'arg3' => [
199 'position' => 2,
200 'byReference' => false,
201 'array' => false,
202 'optional' => true,
203 'defaultValue' => 42,
204 'allowsNull' => false,
205 'type' => 'string',
206 'hasDefaultValue' => true
207 ]
208 ]
209 ],
210 get_class($mockController)
211 );
212
213 $classSchemaMock = $this->createMock(ClassSchema::class);
214 $classSchemaMock
215 ->expects($this->any())
216 ->method('getMethod')
217 ->with('fooAction')
218 ->willReturn($classSchemaMethod);
219
220 $mockReflectionService = $this->createMock(ReflectionService::class);
221 $mockReflectionService
222 ->expects($this->any())
223 ->method('getClassSchema')
224 ->with(get_class($mockController))
225 ->willReturn($classSchemaMock);
226 $mockController->_set('reflectionService', $mockReflectionService);
227 $mockController->_set('request', $mockRequest);
228 $mockController->_set('arguments', $mockArguments);
229 $mockController->_set('actionMethodName', 'fooAction');
230 $mockController->_call('initializeActionMethodArguments');
231 }
232
233 /**
234 * @test
235 */
236 public function initializeActionMethodArgumentsThrowsExceptionIfDataTypeWasNotSpecified(): void
237 {
238 $this->expectException(InvalidArgumentTypeException::class);
239 $this->expectExceptionCode(1253175643);
240 $mockRequest = $this->createMock(Request::class);
241 $mockArguments = $this->createMock(Arguments::class);
242 $mockController = $this->getAccessibleMock(ActionController::class, ['fooAction'], [], '', false);
243
244 $classSchemaMethod = new ClassSchema\Method(
245 'fooAction',
246 [
247 'params' => [
248 'arg1' => [
249 'position' => 0,
250 'byReference' => false,
251 'array' => false,
252 'optional' => false,
253 'allowsNull' => false
254 ]
255 ]
256 ],
257 get_class($mockController)
258 );
259
260 $classSchemaMock = $this->createMock(ClassSchema::class);
261 $classSchemaMock
262 ->expects($this->any())
263 ->method('getMethod')
264 ->with('fooAction')
265 ->willReturn($classSchemaMethod);
266
267 $mockReflectionService = $this->createMock(ReflectionService::class);
268 $mockReflectionService
269 ->expects($this->any())
270 ->method('getClassSchema')
271 ->with(get_class($mockController))
272 ->willReturn($classSchemaMock);
273 $mockController->_set('reflectionService', $mockReflectionService);
274 $mockController->_set('request', $mockRequest);
275 $mockController->_set('arguments', $mockArguments);
276 $mockController->_set('actionMethodName', 'fooAction');
277 $mockController->_call('initializeActionMethodArguments');
278 }
279
280 /**
281 * @test
282 * @dataProvider templateRootPathDataProvider
283 * @param array $configuration
284 * @param array $expected
285 */
286 public function setViewConfigurationResolvesTemplateRootPathsForTemplateRootPath($configuration, $expected)
287 {
288 /** @var ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $mockController */
289 $mockController = $this->getAccessibleMock(ActionController::class, ['dummy'], [], '', false);
290 /** @var ConfigurationManagerInterface|\PHPUnit_Framework_MockObject_MockObject $mockConfigurationManager */
291 $mockConfigurationManager = $this->createMock(ConfigurationManagerInterface::class);
292 $mockConfigurationManager->expects($this->any())->method('getConfiguration')->will($this->returnValue($configuration));
293 $mockController->injectConfigurationManager($mockConfigurationManager);
294 $mockController->_set('request', $this->createMock(Request::class), ['getControllerExtensionKey']);
295 $view = $this->getMockBuilder(ViewInterface::class)
296 ->setMethods(['setControllerContext', 'assign', 'assignMultiple', 'canRender', 'render', 'initializeView', 'setTemplateRootPaths'])
297 ->getMock();
298 $view->expects($this->once())->method('setTemplateRootPaths')->with($expected);
299 $mockController->_call('setViewConfiguration', $view);
300 }
301
302 /**
303 * @return array
304 */
305 public function templateRootPathDataProvider()
306 {
307 return [
308 'text keys' => [
309 [
310 'view' => [
311 'templateRootPaths' => [
312 'default' => 'some path',
313 'extended' => 'some other path'
314 ]
315 ]
316 ],
317 [
318 'extended' => 'some other path',
319 'default' => 'some path'
320 ]
321 ],
322 'numerical keys' => [
323 [
324 'view' => [
325 'templateRootPaths' => [
326 '10' => 'some path',
327 '20' => 'some other path',
328 '15' => 'intermediate specific path'
329 ]
330 ]
331 ],
332 [
333 '20' => 'some other path',
334 '15' => 'intermediate specific path',
335 '10' => 'some path'
336 ]
337 ],
338 'mixed keys' => [
339 [
340 'view' => [
341 'templateRootPaths' => [
342 '10' => 'some path',
343 'very_specific' => 'some other path',
344 '15' => 'intermediate specific path'
345 ]
346 ]
347 ],
348 [
349 '15' => 'intermediate specific path',
350 'very_specific' => 'some other path',
351 '10' => 'some path'
352 ]
353 ],
354 ];
355 }
356
357 /**
358 * @test
359 * @dataProvider layoutRootPathDataProvider
360 *
361 * @param array $configuration
362 * @param array $expected
363 */
364 public function setViewConfigurationResolvesLayoutRootPathsForLayoutRootPath($configuration, $expected)
365 {
366 /** @var ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $mockController */
367 $mockController = $this->getAccessibleMock(ActionController::class, ['dummy'], [], '', false);
368 /** @var ConfigurationManagerInterface|\PHPUnit_Framework_MockObject_MockObject $mockConfigurationManager */
369 $mockConfigurationManager = $this->createMock(ConfigurationManagerInterface::class);
370 $mockConfigurationManager->expects($this->any())->method('getConfiguration')->will($this->returnValue($configuration));
371 $mockController->injectConfigurationManager($mockConfigurationManager);
372 $mockController->_set('request', $this->createMock(Request::class), ['getControllerExtensionKey']);
373 $view = $this->getMockBuilder(ViewInterface::class)
374 ->setMethods(['setControllerContext', 'assign', 'assignMultiple', 'canRender', 'render', 'initializeView', 'setlayoutRootPaths'])
375 ->getMock();
376 $view->expects($this->once())->method('setlayoutRootPaths')->with($expected);
377 $mockController->_call('setViewConfiguration', $view);
378 }
379
380 /**
381 * @return array
382 */
383 public function layoutRootPathDataProvider()
384 {
385 return [
386 'text keys' => [
387 [
388 'view' => [
389 'layoutRootPaths' => [
390 'default' => 'some path',
391 'extended' => 'some other path'
392 ]
393 ]
394 ],
395 [
396 'extended' => 'some other path',
397 'default' => 'some path'
398 ]
399 ],
400 'numerical keys' => [
401 [
402 'view' => [
403 'layoutRootPaths' => [
404 '10' => 'some path',
405 '20' => 'some other path',
406 '15' => 'intermediate specific path'
407 ]
408 ]
409 ],
410 [
411 '20' => 'some other path',
412 '15' => 'intermediate specific path',
413 '10' => 'some path'
414 ]
415 ],
416 'mixed keys' => [
417 [
418 'view' => [
419 'layoutRootPaths' => [
420 '10' => 'some path',
421 'very_specific' => 'some other path',
422 '15' => 'intermediate specific path'
423 ]
424 ]
425 ],
426 [
427 '15' => 'intermediate specific path',
428 'very_specific' => 'some other path',
429 '10' => 'some path'
430 ]
431 ],
432 ];
433 }
434
435 /**
436 * @test
437 * @dataProvider partialRootPathDataProvider
438 *
439 * @param array $configuration
440 * @param array $expected
441 */
442 public function setViewConfigurationResolvesPartialRootPathsForPartialRootPath($configuration, $expected)
443 {
444 /** @var ActionController|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $mockController */
445 $mockController = $this->getAccessibleMock(ActionController::class, ['dummy'], [], '', false);
446 /** @var ConfigurationManagerInterface|\PHPUnit_Framework_MockObject_MockObject $mockConfigurationManager */
447 $mockConfigurationManager = $this->createMock(ConfigurationManagerInterface::class);
448 $mockConfigurationManager->expects($this->any())->method('getConfiguration')->will($this->returnValue($configuration));
449 $mockController->injectConfigurationManager($mockConfigurationManager);
450 $mockController->_set('request', $this->createMock(Request::class), ['getControllerExtensionKey']);
451 $view = $this->getMockBuilder(ViewInterface::class)
452 ->setMethods(['setControllerContext', 'assign', 'assignMultiple', 'canRender', 'render', 'initializeView', 'setpartialRootPaths'])
453 ->getMock();
454 $view->expects($this->once())->method('setpartialRootPaths')->with($expected);
455 $mockController->_call('setViewConfiguration', $view);
456 }
457
458 /**
459 * @return array
460 */
461 public function partialRootPathDataProvider()
462 {
463 return [
464 'text keys' => [
465 [
466 'view' => [
467 'partialRootPaths' => [
468 'default' => 'some path',
469 'extended' => 'some other path'
470 ]
471 ]
472 ],
473 [
474 'extended' => 'some other path',
475 'default' => 'some path'
476 ]
477 ],
478 'numerical keys' => [
479 [
480 'view' => [
481 'partialRootPaths' => [
482 '10' => 'some path',
483 '20' => 'some other path',
484 '15' => 'intermediate specific path'
485 ]
486 ]
487 ],
488 [
489 '20' => 'some other path',
490 '15' => 'intermediate specific path',
491 '10' => 'some path'
492 ]
493 ],
494 'mixed keys' => [
495 [
496 'view' => [
497 'partialRootPaths' => [
498 '10' => 'some path',
499 'very_specific' => 'some other path',
500 '15' => 'intermediate specific path'
501 ]
502 ]
503 ],
504 [
505 '15' => 'intermediate specific path',
506 'very_specific' => 'some other path',
507 '10' => 'some path'
508 ]
509 ],
510 ];
511 }
512
513 /**
514 * @param FluidTemplateView $viewMock
515 * @param string|null $expectedHeader
516 * @param string|null $expectedFooter
517 * @test
518 * @dataProvider headerAssetDataProvider
519 */
520 public function rendersAndAssignsAssetsFromViewIntoPageRenderer($viewMock, $expectedHeader, $expectedFooter)
521 {
522 $this->mockObjectManager = $this->createMock(ObjectManager::class);
523 $pageRendererMock = $this->getMockBuilder(PageRenderer::class)->setMethods(['addHeaderData', 'addFooterData'])->getMock();
524 if (!$viewMock instanceof FluidTemplateView) {
525 $this->mockObjectManager->expects($this->never())->method('get');
526 } else {
527 $this->mockObjectManager->expects($this->any())->method('get')->with(PageRenderer::class)->willReturn($pageRendererMock);
528 }
529 if (!empty(trim($expectedHeader ?? ''))) {
530 $pageRendererMock->expects($this->once())->method('addHeaderData')->with($expectedHeader);
531 } else {
532 $pageRendererMock->expects($this->never())->method('addHeaderData');
533 }
534 if (!empty(trim($expectedFooter ?? ''))) {
535 $pageRendererMock->expects($this->once())->method('addFooterData')->with($expectedFooter);
536 } else {
537 $pageRendererMock->expects($this->never())->method('addFooterData');
538 }
539 $requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass();
540 $subject = new ActionController('');
541 $viewProperty = new \ReflectionProperty($subject, 'view');
542 $viewProperty->setAccessible(true);
543 $viewProperty->setValue($subject, $viewMock);
544 $objectManagerProperty = new \ReflectionProperty($subject, 'objectManager');
545 $objectManagerProperty->setAccessible(true);
546 $objectManagerProperty->setValue($subject, $this->mockObjectManager);
547
548 $method = new \ReflectionMethod($subject, 'renderAssetsForRequest');
549 $method->setAccessible(true);
550 $method->invokeArgs($subject, [$requestMock]);
551 }
552
553 /**
554 * @return array
555 */
556 public function headerAssetDataProvider()
557 {
558 $viewWithHeaderData = $this->getMockBuilder(FluidTemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
559 $viewWithHeaderData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn('custom-header-data');
560 $viewWithHeaderData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn(null);
561 $viewWithFooterData = $this->getMockBuilder(FluidTemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
562 $viewWithFooterData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn(null);
563 $viewWithFooterData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn('custom-footer-data');
564 $viewWithBothData = $this->getMockBuilder(FluidTemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
565 $viewWithBothData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn('custom-header-data');
566 $viewWithBothData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn('custom-footer-data');
567 $invalidView = $this->getMockBuilder(AbstractTemplateView::class)->disableOriginalConstructor()->getMockForAbstractClass();
568 return [
569 [$viewWithHeaderData, 'custom-header-data', null],
570 [$viewWithFooterData, null, 'custom-footer-data'],
571 [$viewWithBothData, 'custom-header-data', 'custom-footer-data'],
572 [$invalidView, null, null]
573 ];
574 }
575 }