eznix86/pest-plugin-testcontainers
Composer 安装命令:
composer require eznix86/pest-plugin-testcontainers
包简介
Pest plugin for testing containers
README 文档
README
eznix86/pest-plugin-testcontainers is a Laravel-focused Pest plugin that starts containers directly from your tests.
Why use this plugin?
- Test your own containers to ensure it runs smoothly with the expected behavior.
- Spin up services (Postgres, Redis, Meilisearch, MinIO, and more) in tests with minimal setup.
- Integrates with Laravel (
database,cache,scout,storage) in one fluent chain.
Table of Contents
- Requirements
- Install
- Quick Start (Laravel + Pest)
- Function API
- Laravel Helpers
- Expectations
- API Reference
- Docker Comparison
- Local Development
- Troubleshooting
- Notes
- Credits
Requirements
- PHP 8.3+
- Docker running locally
- PestPHP v4
Install
composer require --dev eznix86/pest-plugin-testcontainers
The plugin autoloads itself and auto-registers the helper functions, expectations, and InteractsWithContainers trait for Pest tests.
Quick Start (Laravel + Pest)
1) Make Pest use your Laravel base test case
tests/Pest.php
<?php use Tests\TestCase; pest()->extend(TestCase::class)->in('Feature', 'Unit');
2) (Optional) Add the trait explicitly in your base test case
This is usually not required because the plugin auto-registers InteractsWithContainers for Pest tests.
tests/TestCase.php
<?php namespace Tests; use Eznix86\PestPluginTestContainers\InteractsWithContainers; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use InteractsWithContainers; }
3) Write your first container test
it('starts nginx and exposes a mapped port', function () { $container = $this->container('nginx:alpine') ->ports([80]) ->waitForHttp(path: '/', port: 80) ->start(); expect($container->getGeneratedPortFor(80))->toBeInt(); });
Function API
You can use either style:
- Trait style:
$this->container('image')->...->start() - Function style:
container('image')->...->start()
Function-style example:
use function Eznix86\PestPluginTestContainers\container; it('starts nginx with the helper function', function () { $container = container('nginx:alpine') ->ports([80]) ->waitForHttp(path: '/', port: 80) ->start(); expect($container->getGeneratedPortFor(80))->toBeInt(); });
Note: the function helper must run inside an active Pest test. InteractsWithContainers is auto-registered by the plugin (or can be added manually).
Laravel Helpers
Specialized helpers are available for common Laravel services:
postgres(?string $version = null)mysql(?string $version = null)mariadb(?string $version = null)redis(?string $version = null)typesense(?string $version = null)meilisearch(?string $version = null)minio(?string $version = null)garage(?string $version = null)
All specialized builders also support:
->image(string $image)to override the helper image
Database (asDatabase)
Use postgres(), mysql(), or mariadb() with asDatabase() to inject a dedicated Laravel database connection and set it as default.
By default, the connection name is randomized per builder (testcontainer_xxx). If reuse('name') is enabled, the connection name matches the reuse name (including perWorker suffixes).
You can read that name from $container->connectionName() after start().
use function Eznix86\PestPluginTestContainers\postgres; it('uses postgres as laravel database', function () { $builder = postgres('16') ->credentials('app_user', 'secret-pass') ->asDatabase(); // random database name by default $container = $builder->start(); $connection = $container->connectionName(); expect(config('database.default'))->toBe($connection) ->and(config("database.connections.{$connection}.driver"))->toBe('pgsql') ->and(config("database.connections.{$connection}.port"))->toBe($container->mappedPort(5432)); });
Cache (asCache)
Use redis()->asCache() to configure Laravel cache against Redis in the test container.
Use postgres()->asCache(), mysql()->asCache(), or mariadb()->asCache() to configure Laravel cache with the database store over the injected database connection.
use function Eznix86\PestPluginTestContainers\redis; it('uses redis as laravel cache', function () { $builder = redis()->asCache(); $container = $builder->start(); $connection = $container->connectionName(); expect(config('cache.default'))->toBe($connection) ->and(config("database.redis.{$connection}.port"))->toBe($container->mappedPort(6379)); });
Search (asSearch)
Use typesense() or meilisearch() with asSearch() to inject Scout config and set scout.driver.
use function Eznix86\PestPluginTestContainers\typesense; it('uses typesense as laravel scout driver', function () { $container = typesense() ->passphrase('typesense-key') ->asSearch() ->start(); expect(config('scout.driver'))->toBe('typesense') ->and(config('scout.typesense.port'))->toBe($container->mappedPort(8108)); });
Storage (asStorage)
Use minio()->asStorage() or garage()->asStorage() to inject an S3 disk and set it as default.
Storage builders support access mode helpers:
->private()(default)->public()
asStorage() always propagates this mode to Laravel disk visibility (filesystems.disks.<disk>.visibility).
use Illuminate\Support\Facades\Storage; use function Eznix86\PestPluginTestContainers\minio; it('uses minio as laravel storage', function () { $builder = minio()->credentials('storage_user', 'storage_pass')->asStorage(); $container = $builder->start(); Storage::disk($container->connectionName())->put('hello.txt', 'ok'); expect('hello.txt')->toBeInStorage(); expect('missing.txt')->toNotBeInStorage(); }); it('uses minio public bucket policy + laravel visibility', function () { $container = minio() ->public() ->asStorage('assets') ->start(); expect(config('filesystems.disks.assets.visibility'))->toBe('public'); });
use function Eznix86\PestPluginTestContainers\garage; it('uses garage storage with custom toml', function () { $container = garage('v2.2.0') ->withTomlConfig('/absolute/path/to/garage.toml') ->public() ->asStorage('garage-assets') ->start(); expect(config('filesystems.disks.garage-assets.visibility'))->toBe('public'); });
Capability notes:
- MinIO
->public()configures Laravel disk visibility and sets anonymous bucket policy. - Garage
->public()configures Laravel disk visibility and enables bucket website mode (garage bucket website --allow). - Garage does not implement S3 ACL/policy endpoints, so it does not offer MinIO-equivalent anonymous S3 bucket policy behavior.
Local public website testing for Garage (no DNS required):
curl -i -H 'Host: test' http://127.0.0.1:<mapped-3902>/ curl -i -H 'Host: test' http://127.0.0.1:<mapped-3902>/path/to/file.jpg
Expectations
$container->expect(...) supports container-focused assertions:
$container->expect('echo hello')->toRunSuccessfully()->toContain('hello')$container->expect('/tmp/file')->toExist()->toBeReadable()->toNotExist()$container->expect('app started')->toBeInLogs()
Global storage expectations (useful with minio()->asStorage()):
expect('path/to/file')->toBeInStorage()expect('path/to/file')->toNotBeInStorage()
API Reference
Builder methods available before start():
// container(...) ->ports(array $ports) ->env(array $env) ->labels(array $labels) ->volume(string $sourcePath, string $containerPath) ->reuse(string $name, bool $perWorker = false) ->command(array $command) ->healthcheck(array $command) ->waitForLog(string $message, bool $regex = false, int $timeoutSeconds = 30, int $pollIntervalMilliseconds = 500) ->waitForHttp( string $path = '/', ?int $port = null, int $expectedStatusCode = 200, Testcontainers\Container\HttpMethod|string $method = Testcontainers\Container\HttpMethod::GET, bool $https = false, bool $allowInsecure = false, int $timeoutSeconds = 30, int $pollIntervalMilliseconds = 500, int $readTimeoutMilliseconds = 1000, array $headers = [], ) ->waitForPort(?int $port = null, int $timeoutSeconds = 30, int $pollIntervalMilliseconds = 500) ->waitForCommand(array $command, int $timeoutSeconds = 30, int $pollIntervalMilliseconds = 500) ->start()
Specialized helper methods:
// postgres(), mysql(), mariadb() ->credentials(string $username, string $password) ->asDatabase(?string $databaseName = null) ->asCache() ->asQueue(?string $connection = null) // redis() ->passphrase(string $phrase) ->asCache() ->asQueue(?string $connection = null) // typesense(), meilisearch() ->passphrase(string $phrase) ->asSearch() // minio(), garage() ->credentials(string $accessKey, string $secretKey) ->public() ->private() ->asStorage(?string $disk = null) // garage() ->withTomlConfig(string $path) // all specialized builders ->reuse(string $name, bool $perWorker = false) ->image(string $image)
Port mapping notes:
ports([80])picks a free host port automatically.ports([80 => 8080])maps container80to host8080.
Expectation methods available from $container->expect(...):
// $container->expect(...) ->toExist() ->toNotExist() ->toBeDirectory() ->toBeReadable() ->toRunSuccessfully() ->toFail() ->toContain(string $needle) ->toBeInLogs()
Started container helpers:
// $container ->connectionName() // nullable string, set when started from specialized builders ->host() ->mappedPort(int $containerPort) ->getGeneratedPortFor(int $containerPort) ->logs() // sanitized logs ->rawLogs() // raw Docker stream output ->exec(array $command) // returns ExecResult with sanitized output ->execRaw(array $command) // returns ExecResult with raw output
Docker Comparison
docker run --rm \ -p 8080:80 \ -v "$(pwd)/tests/Fixtures/nginx/index.html:/usr/share/nginx/html/index.html" \ --health-cmd="test -f /usr/share/nginx/html/index.html" \ nginx:alpine
Equivalent test setup:
$container = $this->container('nginx:alpine') ->ports([80 => 8080]) ->volume(base_path('tests/Fixtures/nginx/index.html'), '/usr/share/nginx/html/index.html') ->healthcheck(['sh', '-lc', 'test -f /usr/share/nginx/html/index.html']) ->start();
Local Development
From the project root:
composer install
composer test
Troubleshooting
- Docker is not running: start Docker Desktop (or your Docker daemon) and rerun tests.
- Container startup timeout: add an explicit wait strategy (
waitForLog,waitForHttp,waitForPort, orwaitForCommand) and increase timeout values. - Port conflicts: prefer dynamic host ports (
ports([80])) over fixed host mappings (ports([80 => 8080])). - Parallel test collisions: for reusable containers in parallel runs, use
->reuse('name', perWorker: true).
Notes
- Started containers are tracked per test class and cleaned up during Laravel teardown.
->reuse('name', perWorker: false)attaches to a running container with that Docker name when available, and keeps it running across test teardown.->reuse('name', perWorker: true)appends a worker token suffix in parallel runs so each worker gets its own reusable container name.- Connection naming follows the same rule: randomized by default, reuse name when
->reuse(...)is configured.
Credits
eznix86/pest-plugin-testcontainers 适用场景与选型建议
eznix86/pest-plugin-testcontainers 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 308 次下载、GitHub Stars 达 15, 最近一次更新时间为 2026 年 02 月 11 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「php」 「plugin」 「laravel」 「containers」 「docker」 「testcontainers」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 eznix86/pest-plugin-testcontainers 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 eznix86/pest-plugin-testcontainers 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 eznix86/pest-plugin-testcontainers 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
CakePHP 4.x AdminLTE Theme.
Plugin for YOURLS. Default tools to use in some laemmi plugins
Alfabank REST API integration
Allows polling user for event dates and times.
Support swipe touch gestures to navigate in paginated lists and on Tidypics image pages.
Add a postscript to every email notification sent out to users.
统计信息
- 总下载量: 308
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 15
- 点击次数: 32
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-11