Skip to content

Instantly share code, notes, and snippets.

@mikestreety
Last active October 13, 2025 11:03
Show Gist options
  • Save mikestreety/c8f99a662a827fabb3b5b8c529340f4a to your computer and use it in GitHub Desktop.
Save mikestreety/c8f99a662a827fabb3b5b8c529340f4a to your computer and use it in GitHub Desktop.
Set up unit testing for a TYPO3 extension
#!/usr/bin/env php
<?php
/**
* TYPO3 Extension Testing Setup Script
*
* This script sets up a TYPO3 extension for testing by:
* - Adding .gitignore file
* - Modifying composer.json with testing dependencies and scripts
* - Creating phpunit.xml configuration
*/
// File content templates
$gitignoreContent = ".Build\ncomposer.lock\n";
$phpunitXmlContent = '<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
backupGlobals="true"
cacheResult="false"
colors="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
beStrictAboutTestsThatDoNotTestAnything="false"
failOnWarning="true"
cacheDirectory="./.Build/.phpunit.cache"
requireCoverageMetadata="false"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="Unit tests">
<directory>Tests/Unit</directory>
</testsuite>
</testsuites>
<coverage
cacheDirectory=".Build/.phpunit.cache"
includeUncoveredFiles="true"
pathCoverage="false"
ignoreDeprecatedCodeUnits="true"
disableCodeCoverageIgnore="true"
>
<report>
<html outputDirectory="./.Build/html-coverage" lowUpperBound="50" highLowerBound="90"/>
</report>
</coverage>
<source>
<include>
<directory suffix=".php">Classes</directory>
</include>
</source>
<php>
<const name="TYPO3_MODE" value="BE"/>
<ini name="display_errors" value="1"/>
<env name="TYPO3_CONTEXT" value="Testing"/>
</php>
</phpunit>
';
if ($argc < 2) {
echo "Usage: php setup-extension-testing.php <extension-folder-path>\n";
exit(1);
}
$extensionPath = rtrim($argv[1], '/');
// Validate extension folder exists
if (!is_dir($extensionPath)) {
echo "Error: Extension folder '$extensionPath' does not exist.\n";
exit(1);
}
// Validate composer.json exists
$composerJsonPath = $extensionPath . '/composer.json';
if (!file_exists($composerJsonPath)) {
echo "Error: composer.json not found in '$extensionPath'.\n";
exit(1);
}
echo "Setting up TYPO3 extension testing for: $extensionPath\n";
/**
* Infer namespace from composer.json autoload psr-4 configuration
*/
function inferNamespace($composerData)
{
if (!isset($composerData['autoload']['psr-4'])) {
throw new Exception("No autoload psr-4 configuration found in composer.json.");
}
foreach ($composerData['autoload']['psr-4'] as $namespace => $path) {
if ($path === 'Classes/' || $path === 'Classes') {
// Remove trailing backslash and return the full namespace
return rtrim($namespace, '\\');
}
}
throw new Exception("Could not infer extension namespace from autoload psr-4 configuration.");
}
/**
* Create .gitignore file
*/
function createGitIgnore($extensionPath)
{
global $gitignoreContent;
$gitignorePath = $extensionPath . '/.gitignore';
file_put_contents($gitignorePath, $gitignoreContent);
echo "✓ Created .gitignore\n";
}
/**
* Modify composer.json with testing configuration
*/
function modifyComposerJson($extensionPath, $namespace, $composer)
{
$composerJsonPath = $extensionPath . '/composer.json';
// Add autoload-dev
if (!isset($composer['autoload-dev'])) {
$composer['autoload-dev'] = [];
}
if (!isset($composer['autoload-dev']['psr-4'])) {
$composer['autoload-dev']['psr-4'] = [];
}
$composer['autoload-dev']['psr-4'][$namespace . "\\Tests\\"] = "Tests";
// Add extra
if (!isset($composer['extra'])) {
$composer['extra'] = [];
}
if (!isset($composer['extra']['typo3/cms'])) {
$composer['extra']['typo3/cms'] = [];
}
$composer['extra']['typo3/cms']['web-dir'] = ".Build/public";
// Add require-dev
if (!isset($composer['require-dev'])) {
$composer['require-dev'] = [];
}
$requireDev = [
"phpunit/phpunit" => "^10.5",
];
foreach ($requireDev as $package => $version) {
if (!isset($composer['require-dev'][$package])) {
$composer['require-dev'][$package] = $version;
}
}
// Add config
if (!isset($composer['config'])) {
$composer['config'] = [];
}
$composer['config']['lock'] = false;
$composer['config']['vendor-dir'] = ".Build/vendor";
if (!isset($composer['config']['allow-plugins'])) {
$composer['config']['allow-plugins'] = [];
}
$composer['config']['allow-plugins']['typo3/cms-composer-installers'] = true;
$composer['config']['allow-plugins']['typo3/class-alias-loader'] = true;
// Add scripts
if (!isset($composer['scripts'])) {
$composer['scripts'] = [];
}
$scripts = [
"clean" => "rm -rf .Build",
"test-unit" => "phpunit --no-coverage",
];
foreach ($scripts as $name => $command) {
$composer['scripts'][$name] = $command;
}
// Write back to file
$newContent = json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents($composerJsonPath, $newContent);
echo "✓ Modified composer.json\n";
}
/**
* Create phpunit.xml configuration
*/
function createPhpUnitXml($extensionPath)
{
global $phpunitXmlContent;
$phpunitPath = $extensionPath . '/phpunit.xml';
file_put_contents($phpunitPath, $phpunitXmlContent);
echo "✓ Created phpunit.xml\n";
}
/**
* Create Tests/Unit directory structure
*/
function createTestDirectories($extensionPath)
{
$testsDir = $extensionPath . '/Tests';
$testsUnitDir = $testsDir . '/Unit';
if (!is_dir($testsDir)) {
mkdir($testsDir, 0755);
}
if (!is_dir($testsUnitDir)) {
mkdir($testsUnitDir, 0755);
}
echo "✓ Created Tests/Unit directory\n";
}
// Main execution
try {
// Load and parse composer.json first
$composerJsonPath = $extensionPath . '/composer.json';
$content = file_get_contents($composerJsonPath);
$composer = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("Invalid JSON in composer.json: " . json_last_error_msg());
}
$namespace = inferNamespace($composer);
echo "✓ Inferred namespace: $namespace\n";
createGitIgnore($extensionPath);
modifyComposerJson($extensionPath, $namespace, $composer);
createPhpUnitXml($extensionPath);
createTestDirectories($extensionPath);
echo "\n✅ Extension testing setup completed successfully!\n";
echo "\nNext steps:\n";
echo "1. Run 'composer install' in the extension folder\n";
echo "2. Add your unit tests to the Tests/Unit directory\n";
echo "3. Run 'composer test-unit' to run tests\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment