Testcontainers for PHP
repository·main·Indexed 42 days ago
https://github.com/testcontainers/testcontainers-phpA 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.
What's inside Testcontainers for PHP
- 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.
Best practices for networking in tests
mainWhen 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.
- Never hardcode localhost ports: Always resolve endpoints dynamically using
Wait strategies overview
mainWait strategies define the conditions that must be met before a container is considered ready for use. By default,GenericContaineruses a running-state check (WaitForContainer). However, for application-level readiness (e.g., a database being ready to accept connections), you should use explicit wait strategies.How to use GenericContainer
mainUse
GenericContainerto 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');Install Testcontainers for PHP and dependencies
mainTo use Testcontainers in your PHP project, install the core package and any client libraries you intend to test against (e.g.,
predis/predisfor Redis) via Composer.composer require --dev testcontainers/testcontainers composer require --dev predis/predisUse OpenSearchContainer for single-node OpenSearch testing
mainThe
OpenSearchContainerclass 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(); }Use MongoDBContainer for MongoDB testing
mainThe
MongoDBContainerclass 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 untilmongoshcan 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(); }Requirements for MongoDB module
mainTo use the
MongoDBContainermodule, you must have the following PHP extension installed and enabled:ext-mongodb
Join an existing Docker network and use aliases
mainTo 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.
Create the network in your terminal:
docker network create my-test-networkIn your PHP code, use
withNetwork()to join the network andwithAliases()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();Requirements for PostgreSQL module
mainTo use the
PostgresContainermodule, your PHP environment must have the following extension installed and enabled:ext-pdo_pgsql
Run Testcontainers inside a container (Docker-in-Docker)
mainIf you are running your PHP tests inside a containerized environment (such as a CI runner), you must ensure the following:
- Mount the Docker socket: The host's Docker socket (e.g.,
/var/run/docker.sock) must be mounted into the test container. - Network Routing: Ensure that the network routing between your test container and the containers started by Testcontainers is valid.
- Host Overrides: Use
TESTCONTAINERS_HOST_OVERRIDEif your specific CI or network topology requires a custom endpoint to reach the started containers.
- Mount the Docker socket: The host's Docker socket (e.g.,
Integrate Testcontainers with Symfony
mainTo use Testcontainers with Symfony (specifically for Doctrine), you can create a custom
ConnectionFactorythat starts a container and provides the connection DSN. Register this factory inconfig/packages/test/services.yaml.# config/packages/test/services.yaml parameters: 'doctrine.dbal.connection_factory.class': App\Tests\TestConnectionFactorynamespace 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); } }