How to write tests for database/repository methods in your TYPO3 extensions

These kind of tests can be done by Functional Testing. Functional tests are written to validate the functional requirements or use cases of a software system or component.

In this article, I will show you how to setup and write working functional tests for your TYPO3 extension. To read more about testing in TYPO3, check the official docs here.

This tutorial was tested on Ubuntu 18.04 and on with a TYPO3 9.5 core. But the same steps should work for a TYPO3 10 core just some dependencies which are different.

The focus is on testing repository methods since controller actions are usually “glue code”( Code that just “glues” or merges different functionalities together). Controller actions can usually just be tested by checking the result of the controller action in the browser.

The Scenario

We are going to be using Test Driven Development (TDD). The extension is going to be a products extension with the following functionalities.

  • Show all products.
  • Update a product.
  • Truncate Products database table

I am going to explain only the writing of the “Truncate Products database table” functionality. To see how tests for other use cases were written, check the full extension code on Github.

Setup testing environment

Install Git, docker and docker-compose.

Run sudo apt-get install git docker docker-compose in the terminal.

Make sure your local user is part of the docker group. If you don’t do this, you will get a “docker.sock: connect: permission denied” error when running the test execution script.

If you have already setup DDEV before, then you must have already done this. If installing docker for the first time, then you should do this. Follow the official docker docs here on how to do this.

Setup extension for testing

If starting from scratch and have no extension files, then you can create an extension skeleton using the Extension Builder extension.

On an existing TYPO3 project, install the Extension Builder extension and use it to create a skeleton for your extension. If you have no existing TYPO3 project, you can set up one using the steps here.

Do not install the extension after creating it.

Turn the extension’s composer.json file into a root composer.json file

This makes it possible for us to setup a full TYPO3 environment/project in a sub folder of the extension and execute the tests within this sub folder.

The following “root-only” properties below were added to the extension’s composer.json file. To see how these properties were added, check the full extension example on Github.

Below are the different properties which were added with some brief explanations.

  • config: Here we specify the vendor and binary directories for the TYPO3 instance which will be set up in our extension.
  • require-dev: Here, we added the typo3/testing-framework package which we will need for testing and also some additional core extensions.
  • autoload-dev: Here we tell composer that our test classes are found in the Tests directory.
  • scripts
  • extra
    • app-dir
    • web-dir: We set the document root directory of our test TYPO3 instance.
    • extension-key: The name of the extension directory in the test TYPO3 instance which the current extension directory will be linked to.

Gitignore files

Add the code below to the extension’s .gitignore file.

.Build/
.idea/
Build/testing-docker/.env
composer.lock

.Build and Build/testing-docker/.env are runtime on-the-fly files and do not contribute to the extension’s functionality.

Add runTests.sh and docker-compose.yml files

From the example extension here, copy the Build/Scripts/runTests.sh and Build/testing-docker/docker-compose.yml files into your extension.

In these 2 files above, search for all occurrences of the word “my_product” and replace with the value of the “extension-key” property you set in your extension’s composer.json file.

Run Build/Scripts/runTests.sh -s composerInstall from the root directory of our extension to download a basic TYPO3 instance into the .Build/ folder of our extension. This is where we are going to be running our tests.

If you made any errors while setting up, you can just delete and re-download the TYPO3 instance again. Run the commands below for that.

rm -r .Build/ composer.lock
Build/Scripts/runTests.sh -s composerInstall

Now, test if the test environment has been successfully setup. Run the command below to run the Unit test suite.

Build/Scripts/runTests.sh

If the tests are run, then the test environment has been successfully set up. You can begin writing your functional tests and implementing your use cases.

Writing the functional tests

Since we are using TDD, we are first going to write the test for our use case, then make the implementations until the test is passed/successful.

Functional tests for a class in the Classes/ folder should be put in a file with the same name as the class being tested plus an additional Test.php suffix. This file should be put in the same location under the Tests/Functional/ folder as the class to be tested is under the Classes/ folder.

For example, functional tests for methods in the Classes/Domain/Repository/ProductRepository.php file will be put in the Tests/Functional/Domain/Repository/ProductRepositoryTest.php file.

Below is part of the code for the ProductRepositoryTest test case showing test for the “Truncate Products database table” use case.

namespace Mbigha\MyProduct\Tests\Functional\Domain\Repository;

use Mbigha\MyProduct\Domain\Repository\ProductRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Core\Database\ConnectionPool;

/**
 * Test case
 */
class ReferencesRepositoryTest extends FunctionalTestCase
{
    /**
     * @var array Load required extensions
     */
    protected $testExtensionsToLoad = [
        'typo3conf/ext/my_product'
    ];

    /**
     * @var ProductRepository
     */
    private $subject = null;

    private $connection = null;

    /**
     * @var PersistenceManager
     */
    private $persistenceManager = null;

    /**
     * @var Typo3QuerySettings
     */
    private $querySettings = null;

    protected function setUp(): void
    {
        parent::setUp();

        $this->persistenceManager = GeneralUtility::makeInstance(PersistenceManager::class);
        $this->connection = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getConnectionForTable('tx_myproduct_domain_model_product');

        /** @var ObjectManager $objectManager */
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
        $this->querySettings = $objectManager->get(Typo3QuerySettings::class);
        $this->querySettings->setRespectStoragePage(FALSE);

        $this->subject = $objectManager->get(ProductRepository::class);
        $this->subject->setDefaultQuerySettings($this->querySettings);
    }

    /**
     * @test
     */
    public function trunctateProductsTableSuccessfullyTruncatesProductsTable(): void
    {
        $this->importDataSet(__DIR__ . '/Fixtures/tx_myproduct_domain_model_product.xml');

        $numberOfItemsInDatabase = $this->connection->count('uid', 'tx_myproduct_domain_model_product', ['deleted' => 0]);
        $this->assertEquals($this->subject->countAll(), $numberOfItemsInDatabase);
        $this->subject->trunctateProductsTable();

        $numberOfItemsInDatabase = $this->connection->count('uid', 'tx_myproduct_domain_model_product', ['deleted' => 0]);
        $this->assertEquals($numberOfItemsInDatabase, 0);
    }

Check the example extension code to see the implementation of the truncateProductsTable() method which was being tested here.

From the root directory of your extension, run your functional tests with

Build/Scripts/runTests.sh -s functional

You can also run only the tests found in a specific folder. For example to run the tests found in the Tests/Functional/Domain/Repository/ folder of my extension, I use

Build/Scripts/runTests.sh -s functional Tests/Functional/Domain/Repository/

If your test fails, continue making adjustments to your code and running the test again until you get a screenshot similar like below indicating a successful test.

Test run forTruncate Products database table” test above

Notes

  • If you want to load another custom built extension into the test TYPO3 instance, copy the extension into the .Build/Web/typo3conf/ folder and include it in the $testExtensionsToLoad array.
  • Single tests in a test case should be annotated with @test.
  • Test names should start with the name of the method being tested plus a suffix stating what we are testing in the test.
  • Database(DB) fixtures are files containing test data which can be inserted into the test database tables.
  • Use $this->importDataSet() to insert test data into the test database tables. Look at the example extension for the xml structure of the database(DB) fixtures.
  • In DB fixtures, you must not set test data for all the fields. You just have to set test data only for the fields your are interested in.

More Examples

Below are links to the full example extension code and another extension showing you more examples on how to write your tests.

  1. Full Example Extension used in this tutorial.
  2. Tea Extension: An example extension used in the PHPUnit TYPO3 extension’s manual.

So, that was it for this article. Hope I was able to help you. If you got any suggestions or problems, just let me know.

Leave a comment

Blog at WordPress.com.

Up ↑

Design a site like this with WordPress.com
Get started