Last active
October 13, 2025 11:03
-
-
Save mikestreety/c8f99a662a827fabb3b5b8c529340f4a to your computer and use it in GitHub Desktop.
Set up unit testing for a TYPO3 extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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