konradmichalik / ttt
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.
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- armin/editorconfig-cli: ^2.0
- ergebnis/composer-normalize: ^2.44
- konradmichalik/php-cs-fixer-preset: ^0.2.0
- konradmichalik/php-doc-block-header-fixer: ^0.3.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- rector/rector: ^2.2
- typo3/cms-core: ^13.4 || ^14.0
- typo3/cms-frontend: ^13.4 || ^14.0
Suggests
- typo3/cms-core: Required for TYPO3-specific attributes like InApplicationContext and WithEnvironment (^13.4 || ^14.0)
- typo3/cms-frontend: Required for WithFrontendUser (^13.4 || ^14.0)
This package is auto-updated.
Last update: 2026-07-30 09:49:26 UTC
README
ttt
ttt (TYPO3 Testing Terrarium) is a PHPUnit testing toolbox for TYPO3 extension development. At its core is declarative test sandboxing: TYPO3_CONF_VARS, environment variables, application context & more, put in place via PHP attributes and guaranteed to be cleaned up afterwards, whether the test passes, fails or errors. A Request kit, an Assertion kit, a Contract kit and a Fixture kit round out the rest of everyday TYPO3 test writing.
See Documentation index for the complete list of attributes, kits and concepts.
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 */ } }
See docs/why-not-backupglobals.md for how this differs from PHPUnit's built-in #[BackupGlobals].
🔥 Installation
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 |
|---|---|
#[WithTypo3ConfVars] |
Deep-merges configuration into $GLOBALS['TYPO3_CONF_VARS'], full restore afterwards |
#[WithTca] |
Deep-merges configuration into $GLOBALS['TCA'][$table], full restore afterwards |
#[WithGlobal] |
Sets an arbitrary $GLOBALS entry, full restore afterwards (incl. previously unset keys) |
#[WithEnvVar] |
Sets an environment variable (putenv(), $_ENV, $_SERVER), restores all three channels |
#[WithEnvironment] |
Bootstraps Environment::initialize() in a temporary project directory incl. cleanup |
#[InApplicationContext] |
Switches the TYPO3 application context for one test |
#[WithSingleton] |
Registers a singleton via GeneralUtility, restores the previous singleton map |
#[WithBackendUser] |
Provides a lightweight $GLOBALS['BE_USER'] stub and the matching Context backend.user aspect |
#[WithFrontendUser] |
Provides a lightweight $GLOBALS['FE_USER'] stub and the matching Context frontend.user aspect |
#[FreezeTime] |
Pins the Context date aspect and EXEC_TIME globals |
#[InTimeZone] |
Sets the default timezone (date_default_timezone_set()) |
#[InLocale] |
Sets the locale (setlocale()) for a given category |
#[WithStaticProperty] |
Generic escape hatch: overwrites any static property via reflection, full restore afterwards |
#[WithInstance] |
Queues a fake via GeneralUtility::addInstance() for the next makeInstance() call |
Kits
| Kit | Purpose |
|---|---|
| Request kit | Fluent builder for TYPO3 ServerRequest objects |
| Assertion kit | JSON path assertions with descriptive failure messages |
| Contract kit | Generates violation-case tests from a validateConfiguration()-style contract |
| Fixture kit | Disposable test fixtures (images, log files) |
See docs/lifecycle.md for how attribute application and restoration are wired into PHPUnit's event lifecycle, docs/why-extension.md for why that's an extension instead of tearDown(), and docs/without-extension.md for the imperative alternative.
🧩 Extending
Custom attributes are two small classes: a DTO implementing TttAttribute and an AttributeHandler (a public API with a backward-compatibility promise) that applies the state and returns a restorer closure. Handlers must be stateless: all captured state belongs into the closure.
Register custom handlers via a comma-separated handlers parameter on the bootstrap extension; there's no need to replace TttExtension:
<extensions> <bootstrap class="KonradMichalik\Ttt\TttExtension"> <parameter name="handlers" value="Vendor\Ext\Tests\Sandbox\MyHandler,Vendor\Ext\Tests\Sandbox\OtherHandler" /> </bootstrap> </extensions>
Custom handlers run after the built-in ones. A missing class or one that doesn't implement AttributeHandler fails fast with an actionable error naming the handlers parameter.
See docs/non-goals.md for what's deliberately out of scope, and why.
🧑💻 Contributing
Please have a look at CONTRIBUTING.md.
⭐ License
This project is licensed under GNU General Public License 3.0 (or later).