---
title: "Functional testing with the TYPO3 testing framework"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:testing-writing-functional@main"
source: "Testing/FunctionalTesting/Index.rst"
modified: "2026-09-17T05:14:57+00:00"
---

# Functional testing with the TYPO3 testing framework

**Sections on this page**

-   [Simple example](https://docs.typo3.org/permalink/t3coreapi:simple-example@main)
-   [Extending setUp](https://docs.typo3.org/permalink/t3coreapi:extending-setup@main)
-   [Loaded extensions](https://docs.typo3.org/permalink/t3coreapi:loaded-extensions@main)
-   [Database fixtures](https://docs.typo3.org/permalink/t3coreapi:database-fixtures@main)
-   [Asserting database](https://docs.typo3.org/permalink/t3coreapi:asserting-database@main)
-   [Loading files](https://docs.typo3.org/permalink/t3coreapi:loading-files@main)
-   [Setting TYPO3_CONF_VARS](https://docs.typo3.org/permalink/t3coreapi:setting-typo3-conf-vars@main)
-   [Frontend tests](https://docs.typo3.org/permalink/t3coreapi:frontend-tests@main)

-   [Introduction](https://docs.typo3.org/permalink/t3coreapi:testing-writing-functional-introduction@main)

## Simple example

At the time of this writing, TYPO3 Core  contains more than 2600 functional tests, so
there are plenty of test files to look at to learn about writing functional tests. Do not
hesitate looking around, there is plenty to discover.

As a starter, let's have a look at a basic scenario from the styleguide example again:

**EXT:styleguide/Tests/Functional/TcaDataGenerator/GeneratorTest.php**

```php
<?php

namespace TYPO3\CMS\Styleguide\Tests\Functional\TcaDataGenerator;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class GeneratorTest extends FunctionalTestCase
{
  /**
   * Have styleguide loaded
   */
  protected array $testExtensionsToLoad = [
    'typo3conf/ext/styleguide',
  ];

  #[Test]
  public function generatorCreatesBasicRecord(): void
  {
    //...
  }
}

```

That's the basic setup needed for a functional test: Extend `FunctionalTestCase`,
declare extension styleguide should be loaded and have a first test.

## Extending `setUp`

Note `setUp()` is not overridden in this case. If you override it, remember to always
call `parent::setUp()` before doing own stuff. An example can be found in
`\TYPO3\CMS\Backend\Tests\Functional\Domain\Repository\Localization\LocalizationRepositoryTest`:

**typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/LocalizationRepositoryTest.php**

```php
<?php

declare(strict_types=1);

namespace TYPO3\CMS\Backend\Tests\Functional\Domain\Repository\Localization;

use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

/**
 * Test case
 */
class LocalizationRepositoryTest extends FunctionalTestCase
{
  /**
   * @var LocalizationRepository
   */
  protected $subject;

  /**
   * Sets up this test case.
   */
  protected function setUp(): void
  {
    parent::setUp();

    $this->importCSVDataSet(__DIR__ . '/Fixtures/be_users.csv');
    $this->setUpBackendUser(1);
    Bootstrap::initializeLanguageObject();

    $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/Fixtures/DefaultPagesAndContent.csv');

    $this->subject = new LocalizationRepository();
  }

  // ...
}

```

The above example overrides `setUp()` to first call `parent::setUp()`. This is
critically important to do, if not done the entire test instance set up is not triggered.
After calling parent, various things needed by all tests of this scenario are added: A database
fixture is loaded, a backend user is added, the language object is initialized
and an instance of the system under test is parked as `$this->subject` within the class.

## Loaded extensions

The `FunctionalTestCase` has a couple of defaults and properties to specify the set of
loaded extensions of a test case: First, there is a set of default Core extensions that are
always loaded. Those should be `require` or at least `require-dev` dependencies in a
[`composer.json`](../../ExtensionArchitecture/FileStructure/ComposerJson.md#file-extension-composer-json) file, too: `core`, `backend`, `frontend`, `extbase` and `install`.

Apart from that default list, it is possible to load additional Core extensions: An extension
that wants to test if it works well together with workspaces, would for example specify
the workspaces extension as additional to-load extension:

**EXT:my_extension/Tests/Functional/SomeTest.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  protected array $coreExtensionsToLoad = [
    'workspaces',
  ];

  #[Test]
  public function somethingWithWorkspaces(): void
  {
    //...
  }
}

```

Furthermore, third party extensions and fixture extensions can be loaded for
any given test case:

**EXT:my_extension/Tests/Functional/SomeTestExtensions.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  protected array $testExtensionsToLoad = [
    'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
    'typo3conf/ext/base_extension',
  ];

  #[Test]
  public function somethingWithExtensions(): void
  {
    //...
  }
}

```

In this case the fictional extension `some_extension` comes with an own fixture extension that should
be loaded, and another `base_extension` should be loaded. These extensions will be linked into
`typo3conf/ext` of the test case instance.

The functional test bootstrap links all extensions to either `typo3/sysext` for Core extensions or
`typo3conf/ext` for third party extensions, creates a `PackageStates.php` and then uses the
database schema analyzer to create all database tables specified in the [`ext_tables.sql`](../../ExtensionArchitecture/FileStructure/ExtTablesSql.md#file-extension-ext-tables-sql) files.

## Database fixtures

To populate the test database tables with rows to prepare any given scenario, the helper method
`$this->importCSVDataSet()` can be used. Note it is not possible to inject a fully prepared
database, for instance it is not possible to provide a full `.sqlite` database and work on this
in the test case. Instead, database rows should be provided as `.csv` files to be loaded into
the database using `$this->importCSVDataSet()`. An example file could look like this:

**A CSV data set**

```plaintext
"pages"
,"uid","pid","sorting","deleted","t3_origuid","title"
,1,0,256,0,0,"Connected mode"

"tt_content"
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l10n_source","t3_origuid","header"
,297,1,256,0,0,0,0,0,"Regular Element #1"

```

This file defines one row for the `pages` table
and one `tt_content` row. So one `.csv` file can contain rows of multiple tables.

> [!NOTE]
> If you need to define a `null` value within CSV files, you need to use the special value `"NULL"`.

> [!NOTE]
> **Changed in version Testing Framework 8**
>
> There was a similar method called `$this->importDataSet()` that allowed
> loading database rows defined as XML instead of CSV. It was deprecated
> in testing framework 7 and removed with 8.

In general, the methods need the absolute path to the fixture file to load them. However some
keywords are allowed:

**EXT:some_extension/Tests/Functional/SomeTestImportDataSet.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  #[Test]
  public function importData(): void
  {
    // Load a CSV file relative to test case file
    $this->importCSVDataSet(__DIR__ . '/../Fixtures/pages.csv');
    // Load a CSV file of some extension
    $this->importCSVDataSet('EXT:frontend/Tests/Functional/Fixtures/pages-title-tag.csv');
    // Load a CSV file provided by the typo3/testing-framework package
    $this->importCSVDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/pages.csv');
  }
}

```

## Asserting database

A test that triggered some data munging in the database probably wants to test if the final
state of some rows in the database is as expected after the job is done. The helper method
`assertCSVDataSet()` helps to do that. As in the `.csv` example above, it needs the
absolute path to some CSV file that can contain rows of multiple tables. The methods will
then look up the according rows in the database and compare their values with the fields
provided in the CSV files. If they are not identical, the test will fail and output a table
which field values did not match.

## Loading files

If the system under test works on files, those can be provided by the test setup, too. As
example, one may want to check if an image has been properly sized down. The image to work
on can be linked into the test instance:

**EXT:my_extension/Tests/Functional/SomeTestFiles.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  protected array $pathsToLinkInTestInstance = [
    'typo3/sysext/impexp/Tests/Functional/Fixtures/Folders/fileadmin/user_upload/typo3_image2.jpg' => 'fileadmin/user_upload/typo3_image2.jpg',
  ];

  #[Test]
  public function somethingWithFiles(): void
  {
    //...
  }
}

```

It is also possible to *copy* the files to the test instance instead of only linking it
using `$pathsToProvideInTestInstance`.

## Setting TYPO3_CONF_VARS

A default `config/system/settings.php` file of the instance is created by the default `setUp()`.
It contains the database credentials and everything else to end up with a working TYPO3 instance.

If extensions need additional settings in `config/system/settings.php`, the property
`$configurationToUseInTestInstance` can be used to specify these:

**EXT:my_extension/Tests/Functional/SomeTestConfiguration.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use Symfony\Component\Mailer\Transport\NullTransport;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  protected array $configurationToUseInTestInstance = [
    'MAIL' => [
      'transport' => NullTransport::class,
    ],
  ];

  #[Test]
  public function something(): void
  {
    //...
  }
}

```

## Frontend tests

To prepare a frontend test, the system can be instructed to load a set of
`.typoscript` files for a working frontend:

**EXT:my_extension/Tests/Functional/SomeTestFrontend.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class SomeTest extends FunctionalTestCase
{
  #[Test]
  public function somethingWithWorkspaces(): void
  {
    $this->setUpFrontendRootPage(
      1,
      ['EXT:fluid_test/Configuration/TypoScript/Basic.typoscript'],
    );
  }
}

```

This instructs the system to load the `Basic.typoscript` as TypoScript
file for the frontend page with uid 1.

A frontend request can be executed calling `$this->executeFrontendSubRequest()`. It will
return a Response object to be further worked on, for instance it is possible to verify
if the body `->getBody()` contains some string.
