Skip to content

netresearch/typo3-testing-skill

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TYPO3 Testing Skill

A comprehensive Claude Code skill for creating and managing TYPO3 extension tests.

Features

  • Test Creation: Generate Unit, Functional, and Acceptance tests
  • Infrastructure Setup: Automated testing infrastructure installation
  • CI/CD Integration: GitHub Actions and GitLab CI templates
  • Quality Tools: PHPStan, Rector, php-cs-fixer integration
  • Fixture Management: Database fixture templates and tooling
  • Test Orchestration: runTests.sh script pattern from TYPO3 best practices

Installation

Install the skill globally in Claude Code:

cd ~/.claude/skills
git clone https://github.com/netresearch/typo3-testing-skill.git typo3-testing

Or via Claude Code marketplace:

/plugin marketplace add netresearch/claude-code-marketplace
/plugin install typo3-testing

Quick Start

  1. Setup testing infrastructure:

    cd your-extension
    ~/.claude/skills/typo3-testing/scripts/setup-testing.sh
  2. Generate a test:

    ~/.claude/skills/typo3-testing/scripts/generate-test.sh unit MyService
  3. Run tests:

    Build/Scripts/runTests.sh -s unit
    composer ci:test

Test Types

Unit Tests

Fast, isolated tests without external dependencies. Perfect for testing services, utilities, and domain logic.

Functional Tests

Tests with database and full TYPO3 instance. Use for repositories, controllers, and integration scenarios.

Acceptance Tests

Browser-based end-to-end tests using Codeception and Selenium. For testing complete user workflows.

Advanced Testing Patterns

Advanced PHPUnit Configuration

The tea extension demonstrates production-grade PHPUnit configuration with parallel execution, strict mode, and comprehensive coverage analysis.

Parallel Test Execution

PHPUnit 10+ supports parallel test execution for significant performance improvements:

Configuration (Build/phpunit/UnitTests.xml):

<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../../.Build/vendor/phpunit/phpunit/phpunit.xsd"
    executionOrder="random"
    failOnRisky="true"
    failOnWarning="true"
    stopOnFailure="false"
    beStrictAboutTestsThatDoNotTestAnything="true"
    colors="true"
    cacheDirectory=".Build/.phpunit.cache">

    <testsuites>
        <testsuite name="Unit Tests">
            <directory>../../Tests/Unit/</directory>
        </testsuite>
    </testsuites>

    <coverage includeUncoveredFiles="true">
        <report>
            <clover outputFile=".Build/coverage/clover.xml"/>
            <html outputDirectory=".Build/coverage/html"/>
            <text outputFile="php://stdout" showUncoveredFiles="false"/>
        </report>
    </coverage>
</phpunit>

Key Features:

  • executionOrder="random": Detects hidden test dependencies by randomizing test order
  • failOnRisky="true": Treats risky tests as failures (tests without assertions)
  • failOnWarning="true": Fails on warnings like deprecated function usage
  • beStrictAboutTestsThatDoNotTestAnything="true": Ensures every test has assertions

Separate Unit and Functional Configurations

The tea extension maintains separate PHPUnit configurations:

Unit Tests (Build/phpunit/UnitTests.xml):

  • No database bootstrap
  • Fast execution (milliseconds per test)
  • Strict mode enabled
  • Code coverage analysis

Functional Tests (Build/phpunit/FunctionalTests.xml):

  • Database bootstrap included
  • TYPO3 testing framework integration
  • SQLite for fast in-memory testing
  • Test doubles for external services

Example functional test configuration:

<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../../.Build/vendor/phpunit/phpunit/phpunit.xsd"
    stopOnFailure="false"
    colors="true"
    cacheDirectory=".Build/.phpunit.cache">

    <testsuites>
        <testsuite name="Functional Tests">
            <directory>../../Tests/Functional/</directory>
        </testsuite>
    </testsuites>

    <php>
        <ini name="display_errors" value="1"/>
        <env name="TYPO3_CONTEXT" value="Testing"/>
    </php>
</phpunit>

Coverage Thresholds

Enforce minimum coverage requirements via composer scripts:

{
  "scripts": {
    "ci:coverage:check": [
      "@ci:tests:unit",
      "phpunit --configuration Build/phpunit/UnitTests.xml --coverage-text --coverage-clover=.Build/coverage/clover.xml",
      "phpunit-coverage-check .Build/coverage/clover.xml 70"
    ]
  }
}

Progressive Coverage Targets:

  • MVP Extensions: 50% minimum
  • Production Extensions: 70% minimum
  • Reference Extensions: 80%+ target

CSV Fixture and Assertion Pattern

The tea extension demonstrates an elegant CSV-based pattern for functional test fixtures, significantly improving test readability and maintainability.

Problem Statement

Traditional fixture loading in TYPO3 functional tests uses SQL files or PHP arrays:

// ❌ Traditional approach: Verbose and hard to read
protected function setUp(): void
{
    parent::setUp();
    $this->importCSVDataSet(__DIR__ . '/Fixtures/Database/pages.csv');
    $this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tt_content.csv');
    $this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tx_tea_domain_model_product_tea.csv');
}

CSV Fixture Pattern

Fixture File (Tests/Functional/Fixtures/Database/tea.csv):

tx_tea_domain_model_product_tea
uid,pid,title,description,owner
1,1,"Earl Grey","Classic black tea",1
2,1,"Green Tea","Organic green tea",1
3,2,"Oolong Tea","Traditional oolong",2

Loading in Test:

use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

final class TeaRepositoryTest extends FunctionalTestCase
{
    protected array $testExtensionsToLoad = [
        'typo3conf/ext/tea',
    ];

    protected function setUp(): void
    {
        parent::setUp();
        $this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tea.csv');
    }

    /**
     * @test
     */
    public function findAllReturnsAllRecords(): void
    {
        $result = $this->subject->findAll();

        self::assertCount(3, $result);
    }
}

CSV Assertion Pattern

Even More Powerful: Assert database state using CSV format:

Expected State File (Tests/Functional/Fixtures/Database/AssertTeaAfterCreate.csv):

tx_tea_domain_model_product_tea
uid,pid,title,description,owner
1,1,"Earl Grey","Classic black tea",1
2,1,"Green Tea","Organic green tea",1
3,2,"Oolong Tea","Traditional oolong",2
4,1,"New Tea","Newly created tea",1

Assertion in Test:

/**
 * @test
 */
public function createPersistsNewTea(): void
{
    $newTea = new Tea();
    $newTea->setTitle('New Tea');
    $newTea->setDescription('Newly created tea');

    $this->subject->add($newTea);
    $this->persistenceManager->persistAll();

    // Assert entire database state matches expected CSV
    $this->assertCSVDataSet(__DIR__ . '/Fixtures/Database/AssertTeaAfterCreate.csv');
}

Benefits

  1. Readability: CSV format is human-readable and version control friendly
  2. Maintainability: Easy to modify fixtures without PHP syntax knowledge
  3. Comprehensive Assertions: Assert entire table state in single call
  4. Change Detection: Diff tools show fixture changes clearly
  5. Cross-Test Reuse: Same CSV fixtures reusable across multiple tests

Best Practices

Minimal Fixtures: Include only necessary columns for the test:

tx_tea_domain_model_product_tea
uid,title
1,"Earl Grey"
2,"Green Tea"

Named Test Data: Use descriptive titles to make test intent clear:

tx_tea_domain_model_product_tea
uid,title,deleted
1,"Active Tea",0
2,"Deleted Tea",1

Fixture Organization:

Tests/Functional/
├── Fixtures/
│   └── Database/
│       ├── tea_initial.csv           # Initial state
│       ├── tea_after_create.csv      # Expected after creation
│       ├── tea_after_update.csv      # Expected after update
│       └── tea_after_delete.csv      # Expected after deletion

Multi-Database Testing

The tea extension demonstrates comprehensive multi-database testing across SQLite, MariaDB, MySQL, and PostgreSQL, ensuring compatibility across all TYPO3-supported database systems.

Why Multi-Database Testing Matters

Different databases have subtle behavioral differences:

  • SQLite: Case-insensitive LIKE, limited ALTER TABLE support
  • MySQL: Case sensitivity varies by OS and configuration
  • MariaDB: Different optimizer behavior, JSON handling differences
  • PostgreSQL: Strict type casting, different string comparison semantics

Extensions using advanced SQL features (e.g., JSON columns, full-text search, stored procedures) must test across all target databases.

runTests.sh Pattern

The tea extension uses Build/Scripts/runTests.sh for orchestrated multi-database testing:

#!/usr/bin/env bash

# Run functional tests against SQLite (default, fast)
./Build/Scripts/runTests.sh -s functional

# Run functional tests against MariaDB 10.11
./Build/Scripts/runTests.sh -s functional -d mariadb -i 10.11

# Run functional tests against MySQL 8.0
./Build/Scripts/runTests.sh -s functional -d mysql -i 8.0

# Run functional tests against PostgreSQL 16
./Build/Scripts/runTests.sh -s functional -d postgres -i 16

Script Responsibilities:

  1. Docker container orchestration
  2. Database initialization and schema setup
  3. Test execution with proper environment variables
  4. Cleanup and teardown

CI Matrix Configuration

GitHub Actions (.github/workflows/ci.yml):

name: CI

on: [push, pull_request]

jobs:
  functional-tests:
    name: Functional Tests
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php: ['8.2', '8.3', '8.4']
        typo3: ['12.4', '13.0']
        database:
          - type: 'sqlite'
          - type: 'mariadb'
            version: '10.11'
          - type: 'mysql'
            version: '8.0'
          - type: 'postgres'
            version: '16'

    steps:
      - uses: actions/checkout@v4

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: pdo_sqlite, pdo_mysql, pdo_pgsql

      - name: Composer Install
        run: composer install --no-progress

      - name: Functional Tests
        run: |
          if [ "${{ matrix.database.type }}" = "sqlite" ]; then
            ./Build/Scripts/runTests.sh -s functional
          else
            ./Build/Scripts/runTests.sh -s functional -d ${{ matrix.database.type }} -i ${{ matrix.database.version }}
          fi

This matrix runs tests across:

  • 3 PHP versions × 2 TYPO3 versions × 4 databases = 24 test combinations

Database-Specific Considerations

SQLite Advantages:

  • Fast (in-memory execution)
  • No external dependencies
  • Ideal for local development

SQLite Limitations:

// ❌ Won't work on SQLite (lacks ALTER TABLE support)
$connection->executeUpdate('ALTER TABLE tt_content ADD COLUMN new_field VARCHAR(255)');

// ✅ Use TYPO3 API instead (cross-database compatible)
$schemaManager = $connection->getSchemaManager();
$column = new Column('new_field', Type::getType('string'), ['length' => 255]);
$schemaManager->addColumn('tt_content', $column);

PostgreSQL Strict Typing:

// ❌ MySQL/MariaDB allow implicit conversion, PostgreSQL doesn't
$queryBuilder->where(
    $queryBuilder->expr()->eq('uid', '123')  // String '123' vs INT uid
);

// ✅ Explicit type casting works everywhere
$queryBuilder->where(
    $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter(123, \PDO::PARAM_INT))
);

Local Multi-Database Testing

Developers can run multi-database tests locally:

# Quick SQLite test during development
composer ci:tests:functional

# Comprehensive multi-DB test before pushing
./Build/Scripts/runTests.sh -s functional -d mariadb
./Build/Scripts/runTests.sh -s functional -d mysql
./Build/Scripts/runTests.sh -s functional -d postgres

Docker Compose Alternative

For complex scenarios, use docker-compose.yml:

version: '3.8'

services:
  mariadb:
    image: mariadb:10.11
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
    ports:
      - "3306:3306"

  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: root
      POSTGRES_DB: test
    ports:
      - "5432:5432"

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
    ports:
      - "3307:3306"

runTests.sh Orchestration Pattern

The runTests.sh script from the tea extension provides comprehensive test orchestration with Docker-based isolation.

Core Features

1. Test Suite Selection:

./Build/Scripts/runTests.sh -s unit              # Unit tests only
./Build/Scripts/runTests.sh -s functional        # Functional tests
./Build/Scripts/runTests.sh -s acceptance        # Acceptance tests
./Build/Scripts/runTests.sh -s lint              # PHP linting
./Build/Scripts/runTests.sh -s phpstan           # Static analysis

2. Database Selection:

./Build/Scripts/runTests.sh -s functional -d sqlite       # Default
./Build/Scripts/runTests.sh -s functional -d mariadb      # MariaDB
./Build/Scripts/runTests.sh -s functional -d mysql        # MySQL
./Build/Scripts/runTests.sh -s functional -d postgres     # PostgreSQL

3. Version Control:

./Build/Scripts/runTests.sh -s functional -d mariadb -i 10.11
./Build/Scripts/runTests.sh -s functional -d postgres -i 16
./Build/Scripts/runTests.sh -p 8.3              # PHP version

4. Cleanup and Maintenance:

./Build/Scripts/runTests.sh -s clean            # Remove containers
./Build/Scripts/runTests.sh -s composer update  # Update dependencies

Implementation Structure

Key Components:

#!/usr/bin/env bash

# Parse command line arguments
while getopts "s:d:i:p:h" option; do
    case ${option} in
        s) TEST_SUITE=${OPTARG} ;;
        d) DATABASE=${OPTARG} ;;
        i) DATABASE_VERSION=${OPTARG} ;;
        p) PHP_VERSION=${OPTARG} ;;
        h) showHelp; exit 0 ;;
    esac
done

# Set defaults
DATABASE=${DATABASE:-sqlite}
PHP_VERSION=${PHP_VERSION:-8.2}

# Container configuration
CONTAINER_NAME="typo3-testing-${DATABASE}"
DOCKER_IMAGE="typo3/core-testing-${DATABASE}:${DATABASE_VERSION}"

# Execute test suite in container
docker run \
    --name ${CONTAINER_NAME} \
    --rm \
    -v $(pwd):/app \
    -w /app \
    ${DOCKER_IMAGE} \
    /bin/bash -c "composer ci:tests:${TEST_SUITE}"

Benefits

  1. Isolation: Each test run in clean container environment
  2. Reproducibility: Same environment locally and in CI
  3. Version Flexibility: Test against multiple PHP/TYPO3/DB versions
  4. Developer Convenience: Single command for all test types
  5. CI Integration: Same script used locally and in CI

Integration with Composer Scripts

Composer scripts delegate to runTests.sh:

{
  "scripts": {
    "ci:tests:unit": "Build/Scripts/runTests.sh -s unit",
    "ci:tests:functional": "Build/Scripts/runTests.sh -s functional",
    "ci:tests:functional:mariadb": "Build/Scripts/runTests.sh -s functional -d mariadb",
    "ci:tests:functional:postgres": "Build/Scripts/runTests.sh -s functional -d postgres",
    "ci:tests": [
      "@ci:tests:unit",
      "@ci:tests:functional"
    ]
  }
}

This maintains the local-CI parity principle: developers and CI use identical commands.

Example Usage Workflows

Development Workflow:

# Quick unit test during coding
composer ci:tests:unit

# Functional test before commit
composer ci:tests:functional

# Full test suite before push
composer ci:tests

Pre-Release Workflow:

# Test against all databases
./Build/Scripts/runTests.sh -s functional -d sqlite
./Build/Scripts/runTests.sh -s functional -d mariadb -i 10.11
./Build/Scripts/runTests.sh -s functional -d mysql -i 8.0
./Build/Scripts/runTests.sh -s functional -d postgres -i 16

# Test against multiple PHP versions
./Build/Scripts/runTests.sh -s unit -p 8.2
./Build/Scripts/runTests.sh -s unit -p 8.3
./Build/Scripts/runTests.sh -s unit -p 8.4

CI/CD Workflow:

# .github/workflows/ci.yml
- name: Unit Tests
  run: composer ci:tests:unit

- name: Functional Tests (SQLite)
  run: composer ci:tests:functional

- name: Functional Tests (MariaDB)
  run: composer ci:tests:functional:mariadb

- name: Functional Tests (PostgreSQL)
  run: composer ci:tests:functional:postgres

Documentation

  • SKILL.md - Main workflow guide with decision trees
  • references/ - Detailed testing documentation
  • templates/ - PHPUnit configs, AGENTS.md, examples

Requirements

  • PHP 8.1+
  • Composer
  • Docker (for functional and acceptance tests)
  • TYPO3 v12 or v13

Based On

License

GPL-2.0-or-later

Maintained By

Netresearch DTT GmbH, Leipzig

About

Claude Code skill for creating and managing TYPO3 extension tests

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •