Testcontainers for PHP

repository·main·Indexed 42 days ago

https://github.com/testcontainers/testcontainers-php

A PHP library for managing containerized dependencies like databases and message brokers during automated integration and smoke testing. It provides specialized modules for MySQL, MariaDB, PostgreSQL, Redis, and OpenSearch, as well as a GenericContainer for any Docker image. Features include dynamic port mapping, wait strategies, network configuration, and integration guides for Symfony and Doctrine.

Tokens
14.7K
Snippets
61
Records
74
Agent score
49%

What's inside Testcontainers for PHP

  1. What is Testcontainers for PHP

    main
    Testcontainers for PHP is a Composer package designed to simplify the management of container-based dependencies for automated integration and smoke tests. It provides a programmatic API to define, start, and automatically clean up Docker containers as part of your testing lifecycle, ensuring that test dependencies are isolated and ephemeral.
  2. Best practices for networking in tests

    main

    When writing tests with Testcontainers for PHP, follow these networking rules to ensure stability and avoid port collisions:

    • Never hardcode localhost ports: Always resolve endpoints dynamically using $container->getHost() and the mapped port.
    • Use dynamic resolution: Always use $container->getMappedPort($port) or $container->getFirstMappedPort() to find the host-side port.
    • Leverage named networks: For container-to-container communication, use named Docker networks and withAliases() to allow containers to find each other by hostname.
  3. How to use GenericContainer

    main

    Use GenericContainer to start any Docker image. You can configure environment variables, health checks, and volume mounts before starting the container.

    <?php
    
    use Testcontainers\Container\GenericContainer;
    
    $container = new GenericContainer('nginx:alpine');
    
    // set an environment variable
    $container->withEnvironment([
        'key1' => 'val1',
        'key2' => 'val2'
    ]);
    
    // enable health check for a container
    $container->withHealthCheckCommand('curl --fail localhost');
    
    // mount current dir to /var/www/html
    $container->withMount(__DIR__, '/var/www/html');
  4. Use OpenSearchContainer for single-node OpenSearch testing

    main

    The OpenSearchContainer class allows you to spin up an OpenSearch instance configured in single-node mode. It automatically waits for the container to be ready by monitoring startup logs.

    By default, you may want to call withDisabledSecurityPlugin() to simplify testing by disabling the security plugin, which avoids the need for authentication during your tests.

    <?php
    
    declare(strict_types=1);
    
    use Testcontainers//Modules//OpenSearchContainer;
    
    $container = (new OpenSearchContainer())
        ->withDisabledSecurityPlugin()
        ->start();
    
    try {
        // Use $container->getHost() and $container->getFirstMappedPort() to connect
    } finally {
        $container->stop();
    }
  5. Use MongoDBContainer for MongoDB testing

    main

    The MongoDBContainer class allows you to spin up a MongoDB instance in a container. It automatically configures the necessary credentials and implements a wait strategy that ensures the container is ready by waiting until mongosh can successfully execute a command.

    <?php
    
    declare(strict_types=1);
    
    use Testcontainers//Modules//MongoDBContainer;
    
    $container = (new MongoDBContainer())->start();
    
    try {
        $pingResult = $container->exec([
            'mongosh',
            'admin',
            '-u',
            'test',
            '-p',
            'test',
            '--eval',
            '\'db.runCommand("ping").ok\''
        ]);
    
        echo $pingResult;
    } finally {
        $container->stop();
    }
  6. Join an existing Docker network and use aliases

    main

    To enable container-to-container communication, you can connect multiple containers to the same Docker network.

    Note: Testcontainers for PHP does not create Docker networks. You must create the network manually via the Docker CLI before starting your containers.

    1. Create the network in your terminal:

      docker network create my-test-network
    2. In your PHP code, use withNetwork() to join the network and withAliases() to assign a hostname that other containers on that network can use to reach this container.

    <?php
    
    declare(strict_types=1);
    
    use Testcontainers\Container\GenericContainer;
    
    $container = (new GenericContainer('alpine'))
        ->withCommand(['tail', '-f', '/dev/null'])
        ->withNetwork('my-test-network')
        ->withAliases(['service-a'])
        ->start();
    
    $container->stop();
  7. Run Testcontainers inside a container (Docker-in-Docker)

    main

    If you are running your PHP tests inside a containerized environment (such as a CI runner), you must ensure the following:

    1. Mount the Docker socket: The host's Docker socket (e.g., /var/run/docker.sock) must be mounted into the test container.
    2. Network Routing: Ensure that the network routing between your test container and the containers started by Testcontainers is valid.
    3. Host Overrides: Use TESTCONTAINERS_HOST_OVERRIDE if your specific CI or network topology requires a custom endpoint to reach the started containers.
  8. Integrate Testcontainers with Symfony

    main

    To use Testcontainers with Symfony (specifically for Doctrine), you can create a custom ConnectionFactory that starts a container and provides the connection DSN. Register this factory in config/packages/test/services.yaml.

    # config/packages/test/services.yaml
    parameters:
      'doctrine.dbal.connection_factory.class': App\Tests\TestConnectionFactory
    namespace App\Tests;
    
    use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
    use Doctrine\Common\EventManager;
    use Doctrine\DBAL\Configuration;
    use Doctrine\DBAL\Tools\DsnParser;
    use Testcontainers\Modules\PostgresContainer;
    
    class TestConnectionFactory extends ConnectionFactory
    {
        static $testDsn;
    
        public function __construct(array $typesConfig, ?DsnParser $dsnParser = null)
        {
            if (!$this::$testDsn) {
                $psql = (new PostgresContainer())
                    ->withPostgresUser('user')
                    ->withPostgresPassword('password')
                    ->withPostgresDatabase('database')
                    ->start();
                $this::$testDsn = sprintf('postgresql://user:password@%s:%d/database?serverVersion=14&charset=utf8', $psql->getAddress(), $psql->getFirstMappedPort());
            }
            parent::__construct($typesConfig, $dsnParser);
        }
    
    
        public function createConnection(array $params, ?Configuration $config = null, ?EventManager $eventManager = null, array $mappingTypes = [])
        {
            $params['url'] = this::$testDsn;
            return parent::createConnection($params, $config, $eventManager, $mappingTypes);
        }
    
    }