TYPO3 Extension testing

Wolfgang Klinger
4 min readOct 13, 2019

--

TDD and automated software tests is always a topic for developers.

How to definitely start testing your own TYPO3 extensions is a bit challenging to figure out. There is an official documentation, some old articles, some slides, some videos here and here (old) and a best-practice example extension but no complete (if only basic) detailed step-by-step guide. This article is intended to help you get started. 👍

Official TYPO3 Documentation

In this TYPO3 documentation article the why and most of the how is explained. Start by reading this article and then come back here to link the loose ends.

Getting started

I assume you work on a Linux server with composer.

So, first of all you need to add the typo3/testing-framework (an alternative can be found at the end of the article) as dependency to your project.

composer require --dev typo3/testing-framework

This should add the following to the file:

"require-dev": {
"typo3/testing-framework": "*"
}

Create directory structure

As described in the documentation you create a directory Test in your extension directory and add directories for the required type of test, e.g. Unit or Functional and below that even more directories reflecting the structure of your classes.

Should finally look something like this:

├── Classes
└── Test
├── Functional
│ ├── Fixtures
│ │ ├── DatabaseAssertions
│ │ │ └── importSomething.csv
│ │ └── defaultPages.xml
│ └── Service
│ └── ConcreteSomethingImportServiceTest.php
└── Unit
└── Service
└── AbstractSomethingServiceTest.php

So we test a class named AbstractSomethingService with a Unit Test.

Unit Testing

The Unit Test is straight forward and needs not much setup.
Extend your test class (whose name is the class to test with a Test postfix) from TYPO3\TestingFramework\Core\Unit\UnitTestCase:

Let’s take a closer look at some of these lines.

protected $resetSingletonInstances = true;

If you use any code in your extension or from the TYPO3 core that uses GeneralUtility::makeInstance() to create a Singleton you have to set this property to true in order to reset (aka remove) all these instances before each test is run.

If you’re curious, this calls:

\TYPO3\CMS\Core\Utility\GeneralUtility::resetSingletonInstances

Next you add your test methods. You can either name them testThisAndThat (with a test prefix) or add the annotation @test in PHPDoc.

Run the Unit tests

You can run the tests with:

./vendor/bin/phpunit path/to/extension/Test/Unit

and see it pass or fail

.                                                                   1 / 1 (100%)Time: 30 ms, Memory: 6.00 MBOK (1 test, 3 assertions)

Functional testing

Functional testing is a bit more complicated in setup as your code is likely to require a database.

You should definitely consider sqlite as test database. TYPO3 9+ comes with support for this database. Note that this creates publicly accessible new files like /typo3temp/var/test/functional-1c51a2b/test.sqlite in your installation! So you shouldn’t run this on production 😉

First of all you’ll likely need some pages or other records in your database to work with.

You can add them with fixtures either in XML or CSV format. I used XML for example to insert a simple page of type sysfolder ( doctype 255) into the test database before the test is run.

<?xml version="1.0" encoding="utf-8"?>
<dataset>
<pages>
<uid>1</uid>
<pid>0</pid>
<doktype>254</doktype>
<title>Import</title>
<deleted>0</deleted>
<perms_everybody>15</perms_everybody>
</pages>
</dataset>

Save this as Test/Functional/Fixtures/defaultPages.xml .

Next we add our test class ConcreteSomethingImportServiceTest that tests methods of the class ConcreteSomethingImportService. This time we extend the class from TYPO3\TestingFramework\Core\Functional\FunctionalTestCase:

Let’s take a closer look at some of these lines again.

protected $testExtensionsToLoad = [
'typo3conf/ext/my_extension'
];

This tells the system to load your extension while running the tests. Makes sense huh? 👏

protected function setUp(): void    
{
parent::setUp();
// Core DataHandler requires a backend user
$this->setUpBackendUserFromFixture(1);
$this->importDataSet(
'EXT:my_extension/Test/Functional/Fixtures/defaultPages.xml'
);
}

This method is run before every test and it

  • creates a backend user object in $GLOBALS['BE_USER'] that’s required by e.g. the TYPO3\CMS\Core\DataHandling\DataHandler class of the TYPO3 core
  • and imports the fixtures we talked about earlier into the test database

So now we have a page in the database and can import new records in our ConcreteSomethingImportService class method we test here.

Run the Functional tests

This is a bit different than running the Unit tests. We have to specify information for the test database. As we use sqlite here (see above) it’s sufficient to set the environment variable for the database driver typo3DatabaseDriver to pdo_sqlite (default is mysqli). If you want to use another kind of database here’s a complete list of environment variables you can set:

typo3DatabaseName
typo3DatabaseHost
typo3DatabaseUsername
typo3DatabasePassword
typo3DatabasePort
typo3DatabaseSocket
typo3DatabaseDriver
typo3DatabaseCharset

The second thing is you have to specify a bootstrap class for the functional tests. The TYPO3 testing framework already provides this.

The complete working command line looks like this:

$ typo3DatabaseDriver=pdo_sqlite ./vendor/bin/phpunit --bootstrap vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php path/to/extension/Test/Functional

This kind of tests takes a bit more time to run, but you should see it pass or fail again:

.                                                                   1 / 1 (100%)Time: 2.36 seconds, Memory: 28.00 MBOK (1 test, 1 assertion)

If the test fails you’ll get a nice table with expected and actual values compared to the fixtures:

There was 1 failure:1) Vendor\MyExtension\Test\Functional\Service\ConcreteSomethingImportServiceTest::importDoesItRightAssertion in data-set failed for "tx_myextension_domain_model_something:1":Fields    |Assertion                |Recordname      |Example name             |Xample name/app/vendor/typo3/testing-framework/Classes/Core/Functional/FunctionalTestCase.php:525/app/local/my_extension/Test/Functional/Service/ConcreteSomethingImportServiceTest.php:12FAILURES!Tests: 1, Assertions: 1, Failures: 1.

Alternative testing framework

There’s another testing framework for TYPO3 nimut/testing-framework as a drop-in replacement for typo3/testing-framework. Check out their github page for details.

I hope you learned something new from the article and you enjoy testing your TYPO3 extensions! 💪

--

--

Wolfgang Klinger
Wolfgang Klinger

Written by Wolfgang Klinger

Programmer, Photographer, Gardener

No responses yet