ttt - TYPO3 Testing Terrarium. Declarative test sandboxing via PHP attributes: TYPO3_CONF_VARS, application context, environment and more - applied before the test, guaranteed to be restored afterwards.

Maintainers

Package info

github.com/konradmichalik/ttt

pkg:composer/konradmichalik/ttt

Transparency log

Statistics

Installs: 1 487

Dependents: 6

Suggesters: 0

Stars: 1

Open Issues: 0

0.3.0 2026-07-28 06:32 UTC

This package is auto-updated.

Last update: 2026-07-29 14:20:05 UTC


README

ttt

Coverage CGL Tests Supported PHP Versions

ttt (TYPO3 Testing Terrarium) is a PHPUnit testing toolbox for TYPO3 extension development. It puts TYPO3_CONF_VARS, environment variables, application context & more in place declaratively via PHP attributes — guaranteed to be cleaned up afterwards, whether the test passes, fails or errors.

Before:

final class HandlerTest extends TestCase
{
    protected function setUp(): void
    {
        $this->backup = $GLOBALS['TYPO3_CONF_VARS'] ?? [];
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['my_ext']['configuration'] = [];
        putenv('MY_EXT_FEATURE=1');
    }

    protected function tearDown(): void
    {
        $GLOBALS['TYPO3_CONF_VARS'] = $this->backup; // skipped on hard errors!
        putenv('MY_EXT_FEATURE');
    }

    public function testResolvesConfiguration(): void { /* ... */ }
}

After:

#[WithTypo3ConfVars(['EXTCONF' => ['my_ext' => ['configuration' => []]]])]
final class HandlerTest extends TestCase
{
    #[Test]
    #[WithEnvVar('MY_EXT_FEATURE', '1')]
    public function resolvesConfiguration(): void { /* just Arrange–Act–Assert */ }
}

🔥 Installation

Packagist Packagist Downloads

composer require --dev konradmichalik/ttt

⚡ Usage

Register the extension once in your phpunit.xml:

<extensions>
    <bootstrap class="KonradMichalik\Ttt\TttExtension"/>
</extensions>

That's it — all ttt attributes now work in every test. Attributes can be placed on classes and methods (class level is applied first, method level merges on top) and are repeatable.

Available attributes

Attribute Purpose Requires
#[WithTypo3ConfVars([...])] Deep-merges configuration into $GLOBALS['TYPO3_CONF_VARS'], full restore afterwards
#[WithEnvVar('NAME', 'value')] Sets an environment variable (putenv(), $_ENV, $_SERVER), restores all three channels
#[WithEnvironment(...)] Bootstraps Environment::initialize() in a temporary project directory incl. cleanup typo3/cms-core
#[InApplicationContext('Development')] Switches the TYPO3 application context for one test typo3/cms-core
#[WithSingleton(Foo::class, new FakeFoo())] Registers a singleton via GeneralUtility, restores the previous singleton map typo3/cms-core
#[WithBackendUser(admin: true)] Provides a lightweight $GLOBALS['BE_USER'] stub typo3/cms-core
#[FreezeTime('2026-07-14T12:00:00Z')] Pins the Context date aspect and EXEC_TIME globals typo3/cms-core

Request kit

use KonradMichalik\Ttt\Http\Requests;

$request = Requests::post('/api/items')
    ->withJsonBody(['title' => 'Terrarium'])
    ->withRemoteAddress('10.0.0.1')
    ->build(); // TYPO3 ServerRequest incl. normalizedParams attribute

Assertion kit

use KonradMichalik\Ttt\Assertion\JsonAssertions;

final class McpResponseTest extends TestCase
{
    use JsonAssertions;

    #[Test]
    public function returnsItems(): void
    {
        self::assertJsonPath($response, 'result.items.0.uid', 42);
        self::assertJsonHasPaths($response, ['result', 'result.items']);
        self::assertJsonPathEqualsWithDelta($response, 'result.hitRatio', 0.667, 0.001);
    }
}

Contract kit

Describe a validateConfiguration()-style API once — the contract generates the violation cases (missing required key, wrong type, out-of-range):

final class CircleModifierValidationTest extends ConfigurationValidationContract
{
    protected function isValid(array $configuration): bool
    {
        return (new CircleModifier())->validateConfiguration($configuration);
    }

    protected function validConfiguration(): array
    {
        return ['color' => '#ff0000', 'size' => 0.5];
    }

    protected function schema(): array
    {
        return ['color' => 'string', 'size' => 'float:0..1', 'position?' => 'string'];
    }
}

Fixture kit

$png = ImageFixtures::createPng(64, 64);          // disposable GD test image
LogFixtures::write($path, ['line one', 'line two']);

Why an extension instead of tearDown()?

The restore logic is driven by PHPUnit's event system (Test\Finished fires for every test, regardless of outcome). Hand-written tearDown() cleanup can be skipped by hard errors and leak state into subsequent tests — Terrarium can't.

Without the extension

For imperative use (or mid-test changes) the same handlers are available as traits:

use KonradMichalik\Ttt\Traits\ConfVarsSandbox;

final class HandlerTest extends TestCase
{
    use ConfVarsSandbox;

    protected function tearDown(): void
    {
        $this->restoreTypo3ConfVars();
    }

    #[Test]
    public function resolvesConfiguration(): void
    {
        $this->setTypo3ConfVars(['EXTCONF' => ['my_ext' => []]]);
        // ...
    }
}

🧩 Extending

Custom attributes are two small classes: a DTO implementing TttAttribute and an AttributeHandler that applies the state and returns a restorer closure. Handlers must be stateless — all captured state belongs into the closure.

🧑‍💻 Contributing

Please have a look at CONTRIBUTING.md.

⭐ License

This project is licensed under GNU General Public License 3.0 (or later).