2 namespace TYPO3\CMS\Core\Tests
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response
;
20 * Base test case class for functional tests, all TYPO3 CMS
21 * functional tests should extend from this class!
23 * If functional tests need additional setUp() and tearDown() code,
24 * they *must* call parent::setUp() and parent::tearDown() to properly
25 * set up and destroy the test system.
27 * The functional test system creates a full new TYPO3 CMS instance
28 * within typo3temp/ of the base system and the bootstraps this TYPO3 instance.
29 * This abstract class takes care of creating this instance with its
30 * folder structure and a LocalConfiguration, creates an own database
31 * for each test run and imports tables of loaded extensions.
33 * Functional tests must be run standalone (calling native phpunit
34 * directly) and can not be executed by eg. the ext:phpunit backend module.
35 * Additionally, the script must be called from the document root
36 * of the instance, otherwise path calculation is not successfully.
38 * Call whole functional test suite, example:
39 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
40 * - typo3/../bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml
42 * Call single test case, example:
43 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
44 * - typo3/../bin/phpunit \
45 * --process-isolation \
46 * --bootstrap typo3/sysext/core/Build/FunctionalTestsBootstrap.php \
47 * typo3/sysext/core/Tests/Functional/DataHandling/DataHandlerTest.php
49 abstract class FunctionalTestCase
extends BaseTestCase
52 * Core extensions to load.
54 * If the test case needs additional core extensions as requirement,
55 * they can be noted here and will be added to LocalConfiguration
56 * extension list and ext_tables.sql of those extensions will be applied.
58 * This property will stay empty in this abstract, so it is possible
59 * to just overwrite it in extending classes. Extensions noted here will
60 * be loaded for every test of a test case and it is not possible to change
61 * the list of loaded extensions between single tests of a test case.
63 * A default list of core extensions is always loaded.
65 * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
68 protected $coreExtensionsToLoad = array();
71 * Array of test/fixture extensions paths that should be loaded for a test.
73 * This property will stay empty in this abstract, so it is possible
74 * to just overwrite it in extending classes. Extensions noted here will
75 * be loaded for every test of a test case and it is not possible to change
76 * the list of loaded extensions between single tests of a test case.
78 * Given path is expected to be relative to your document root, example:
81 * 'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
82 * 'typo3conf/ext/base_extension',
85 * Extensions in this array are linked to the test instance, loaded
86 * and their ext_tables.sql will be applied.
90 protected $testExtensionsToLoad = array();
93 * Array of test/fixture folder or file paths that should be linked for a test.
95 * This property will stay empty in this abstract, so it is possible
96 * to just overwrite it in extending classes. Path noted here will
97 * be linked for every test of a test case and it is not possible to change
98 * the list of folders between single tests of a test case.
101 * 'link-source' => 'link-destination'
104 * Given paths are expected to be relative to the test instance root.
105 * The array keys are the source paths and the array values are the destination
109 * 'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
110 * 'fileadmin/user_upload',
111 * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
112 * 'uploads/tx_myownext'
115 * To be able to link from my_own_ext the extension path needs also to be registered in
116 * property $testExtensionsToLoad
120 protected $pathsToLinkInTestInstance = array();
123 * This configuration array is merged with TYPO3_CONF_VARS
124 * that are set in default configuration and factory configuration
128 protected $configurationToUseInTestInstance = array();
131 * Array of folders that should be created inside the test instance document root.
133 * This property will stay empty in this abstract, so it is possible
134 * to just overwrite it in extending classes. Path noted here will
135 * be linked for every test of a test case and it is not possible to change
136 * the list of folders between single tests of a test case.
138 * Per default the following folder are created
145 * To create additional folders add the paths to this array. Given paths are expected to be
146 * relative to the test instance root and have to begin with a slash. Example:
149 * 'fileadmin/user_upload'
154 protected $additionalFoldersToCreate = array();
157 * The fixture which is used when initializing a backend user
161 protected $backendUserFixture = 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml';
164 * Private utility class used in setUp() and tearDown(). Do NOT use in test cases!
166 * @var \TYPO3\CMS\Core\Tests\FunctionalTestCaseBootstrapUtility
168 private $bootstrapUtility = null;
171 * Calculate a "unique" identifier for the test database and the
172 * instance patch based on the given test case class name.
176 protected function getInstanceIdentifier()
178 return FunctionalTestCaseBootstrapUtility
::getInstanceIdentifier(get_class($this));
182 * Calculates path to TYPO3 CMS test installation for this test case.
186 protected function getInstancePath()
188 return FunctionalTestCaseBootstrapUtility
::getInstancePath(get_class($this));
192 * Set up creates a test instance and database.
194 * This method should be called with parent::setUp() in your test cases!
198 protected function setUp()
200 if (!defined('ORIGINAL_ROOT')) {
201 $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
203 $this->bootstrapUtility
= new FunctionalTestCaseBootstrapUtility();
204 $this->bootstrapUtility
->setUp(
206 $this->coreExtensionsToLoad
,
207 $this->testExtensionsToLoad
,
208 $this->pathsToLinkInTestInstance
,
209 $this->configurationToUseInTestInstance
,
210 $this->additionalFoldersToCreate
215 * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
217 * This method should be used instead of direct access to
218 * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
220 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
222 protected function getDatabaseConnection()
224 return $GLOBALS['TYPO3_DB'];
228 * Initialize backend user
230 * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
231 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
234 protected function setUpBackendUserFromFixture($userUid)
236 $this->importDataSet(ORIGINAL_ROOT
. $this->backendUserFixture
);
237 $database = $this->getDatabaseConnection();
238 $userRow = $database->exec_SELECTgetSingleRow('*', 'be_users', 'uid = ' . (int)$userUid);
240 /** @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
241 $backendUser = \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Authentication\BackendUserAuthentication
::class);
242 $sessionId = $backendUser->createSessionId();
243 $_COOKIE['be_typo_user'] = $sessionId;
244 $backendUser->id
= $sessionId;
245 $backendUser->sendNoCacheHeaders
= false;
246 $backendUser->dontSetCookie
= true;
247 $backendUser->createUserSession($userRow);
249 $GLOBALS['BE_USER'] = $backendUser;
250 $GLOBALS['BE_USER']->start();
251 if (!is_array($GLOBALS['BE_USER']->user
) ||
!$GLOBALS['BE_USER']->user
['uid']) {
253 'Can not initialize backend user',
257 $GLOBALS['BE_USER']->backendCheckLogin();
263 * Imports a data set represented as XML into the test database,
265 * @param string $path Absolute path to the XML file containing the data set to load
269 protected function importDataSet($path)
271 if (!is_file($path)) {
273 'Fixture file ' . $path . ' not found',
278 $database = $this->getDatabaseConnection();
280 $fileContent = file_get_contents($path);
281 // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
282 $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
283 $xml = simplexml_load_string($fileContent);
284 libxml_disable_entity_loader($previousValueOfEntityLoader);
285 $foreignKeys = array();
287 /** @var $table \SimpleXMLElement */
288 foreach ($xml->children() as $table) {
289 $insertArray = array();
291 /** @var $column \SimpleXMLElement */
292 foreach ($table->children() as $column) {
293 $columnName = $column->getName();
296 if (isset($column['ref'])) {
297 list($tableName, $elementId) = explode('#', $column['ref']);
298 $columnValue = $foreignKeys[$tableName][$elementId];
299 } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) {
302 $columnValue = (string)$table->$columnName;
305 $insertArray[$columnName] = $columnValue;
308 $tableName = $table->getName();
309 $result = $database->exec_INSERTquery($tableName, $insertArray);
310 if ($result === false) {
312 'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName . ': ' . $database->sql_error(),
316 if (isset($table['id'])) {
317 $elementId = (string)$table['id'];
318 $foreignKeys[$tableName][$elementId] = $database->sql_insert_id();
325 * @param array $typoScriptFiles
327 protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array())
329 $pageId = (int)$pageId;
330 $page = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId);
333 $this->fail('Cannot set up frontend root page "' . $pageId . '"');
336 $pagesFields = array(
340 $this->getDatabaseConnection()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields);
342 $templateFields = array(
350 foreach ($typoScriptFiles as $typoScriptFile) {
351 $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF
;
354 $this->getDatabaseConnection()->exec_INSERTquery('sys_template', $templateFields);
359 * @param int $languageId
360 * @param int $backendUserId
361 * @param int $workspaceId
362 * @param bool $failOnFailure
363 * @param int $frontendUserId
366 protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = true, $frontendUserId = 0)
368 $pageId = (int)$pageId;
369 $languageId = (int)$languageId;
371 $additionalParameter = '';
373 if (!empty($frontendUserId)) {
374 $additionalParameter .= '&frontendUserId=' . (int)$frontendUserId;
376 if (!empty($backendUserId)) {
377 $additionalParameter .= '&backendUserId=' . (int)$backendUserId;
379 if (!empty($workspaceId)) {
380 $additionalParameter .= '&workspaceId=' . (int)$workspaceId;
384 'documentRoot' => $this->getInstancePath(),
385 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
388 $template = new \
Text_Template(ORIGINAL_ROOT
. 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl');
391 'arguments' => var_export($arguments, true),
392 'originalRoot' => ORIGINAL_ROOT
,
396 $php = \PHPUnit_Util_PHP
::factory();
397 $response = $php->runJob($template->render());
398 $result = json_decode($response['stdout'], true);
400 if ($result === null) {
401 $this->fail('Frontend Response is empty');
404 if ($failOnFailure && $result['status'] === Response
::STATUS_Failure
) {
405 $this->fail('Frontend Response has failure:' . LF
. $result['error']);
408 $response = new Response($result['status'], $result['content'], $result['error']);