feat: use testcontainers for isolated MySQL in test runs (#153)
## Summary - Add `testcontainers/testcontainers` to `require-dev` to spin up an ephemeral MySQL 8.0 container per test process, eliminating the need for a local database or Docker Compose stack - Create `tests/bootstrap.php` that starts a `MySQLContainer`, sets `DB_*` env vars dynamically, and auto-generates `APP_KEY` when missing (supports fresh worktrees and CI) - Update `phpunit.xml` to use the new bootstrap file and remove the hardcoded `DB_DATABASE` env var ## How it works Running `php artisan test` now automatically starts a short-lived MySQL container on a random port. Each test process gets its own isolated database, so multiple agents, worktrees, or CI jobs can run in parallel without conflicts. Set `TESTCONTAINERS=false` to bypass containers and use the database configured in `.env` instead.
This commit is contained in:
parent
f2a7f955e6
commit
e4243c2eaa
|
|
@ -60,6 +60,7 @@ jobs:
|
|||
- name: Tests
|
||||
run: ./vendor/bin/pest --exclude-testsuite=Browser,Performance
|
||||
env:
|
||||
TESTCONTAINERS: false
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
|
|
@ -118,6 +119,7 @@ jobs:
|
|||
- name: Browser Tests
|
||||
run: ./vendor/bin/pest --testsuite=Browser --ci
|
||||
env:
|
||||
TESTCONTAINERS: false
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
|
|
@ -197,6 +199,7 @@ jobs:
|
|||
- name: Performance Tests
|
||||
run: ./vendor/bin/pest --testsuite=Performance
|
||||
env:
|
||||
TESTCONTAINERS: false
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^4.1",
|
||||
"pestphp/pest-plugin-browser": "^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^4.0"
|
||||
"pestphp/pest-plugin-laravel": "^4.0",
|
||||
"testcontainers/testcontainers": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
|
|
@ -29,7 +29,6 @@
|
|||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_DATABASE" value="testing"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
use Testcontainers\Modules\MySQLContainer;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Bootstrap — Testcontainers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This bootstrap starts an ephemeral MySQL container before any tests run.
|
||||
| Each test process gets its own isolated database on a random port, so
|
||||
| multiple agents, worktrees, or CI jobs can run tests in parallel
|
||||
| without conflicts.
|
||||
|
|
||||
| Set TESTCONTAINERS=false to skip container creation and use the
|
||||
| database configured in your .env file instead.
|
||||
|
|
||||
*/
|
||||
|
||||
$useContainers = filter_var(
|
||||
$_SERVER['TESTCONTAINERS'] ?? $_ENV['TESTCONTAINERS'] ?? 'true',
|
||||
FILTER_VALIDATE_BOOLEAN,
|
||||
);
|
||||
|
||||
// Autoload must be available before anything else
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Create a minimal .env file when none exists so PHPDotenv does not
|
||||
// emit a file_get_contents warning on every single test.
|
||||
$envPath = __DIR__.'/../.env';
|
||||
if (! file_exists($envPath)) {
|
||||
file_put_contents($envPath, "APP_ENV=testing\n");
|
||||
}
|
||||
|
||||
if ($useContainers) {
|
||||
$container = (new MySQLContainer('8.0'))
|
||||
->withMySQLDatabase('testing')
|
||||
->withMySQLUser('testing', 'testing')
|
||||
->start();
|
||||
|
||||
// Stop and remove the container when the PHP process exits, even on
|
||||
// crashes or SIGINT. testcontainers-php has no built-in cleanup.
|
||||
register_shutdown_function(function () use ($container): void {
|
||||
$container->stop();
|
||||
});
|
||||
|
||||
$host = $container->getHost();
|
||||
$port = (string) $container->getFirstMappedPort();
|
||||
|
||||
// Set environment variables before Laravel boots so config/database.php
|
||||
// reads the dynamic host and port via env(). Both putenv() and $_ENV/$_SERVER
|
||||
// are needed because Laravel's env() helper checks multiple sources.
|
||||
$env = [
|
||||
'DB_CONNECTION' => 'mysql',
|
||||
'DB_HOST' => $host,
|
||||
'DB_PORT' => $port,
|
||||
'DB_DATABASE' => 'testing',
|
||||
'DB_USERNAME' => 'testing',
|
||||
'DB_PASSWORD' => 'testing',
|
||||
];
|
||||
|
||||
// Generate an APP_KEY if one is not already set, so tests can run
|
||||
// without a .env file (e.g. fresh worktrees, CI environments).
|
||||
if (empty($_SERVER['APP_KEY'] ?? $_ENV['APP_KEY'] ?? getenv('APP_KEY'))) {
|
||||
$env['APP_KEY'] = 'base64:'.base64_encode(random_bytes(32));
|
||||
}
|
||||
|
||||
foreach ($env as $key => $value) {
|
||||
putenv("{$key}={$value}");
|
||||
$_ENV[$key] = $value;
|
||||
$_SERVER[$key] = $value;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue