ihsw/toxiproxy-php-client
A PHP client for Toxiproxy, the TCP proxy for simulating network conditions in tests. Create and manage proxies and toxics to inject latency, bandwidth limits, timeouts, and connection failures from PHP applications and test suites.
Install the client as a dev dependency — it is only useful in test/CI environments:
composer require --dev ihsw/toxiproxy-php-client
The client does not start Toxiproxy; you must run the Toxiproxy server separately. The quickest path is Docker:
docker run -d --name toxiproxy \
-p 8474:8474 \
-p 26379:26379 \
shopify/toxiproxy
Then point the client at the HTTP API and create your first proxy:
use Toxiproxy\Toxiproxy;
$toxiproxy = new Toxiproxy('http://127.0.0.1:8474');
// name, upstream target, optional listen address
$proxy = $toxiproxy->create('redis', '127.0.0.1:6379', '127.0.0.1:26379');
// Simulate 500ms of downstream latency
$proxy->addToxic('latency', ['latency' => 500]);
Point your application’s Redis/MySQL/HTTP config at the proxy’s listen address (127.0.0.1:26379 above), and the test exercises your retry, timeout, fallback, or circuit-breaker logic instead of the real service.
Where to look first:
Toxiproxy\Toxiproxy — proxy lifecycle (create, get, all, delete, reset)Toxiproxy\Proxy — per-proxy state and toxic management (addToxic, removeToxic, getToxics, setEnabled)Toxiproxy\Toxic — returned toxic attributesFirst useful use case: a PHPUnit test that adds a latency toxic to a Redis proxy and asserts your client retries or degrades gracefully, then removes the toxic and verifies recovery.
Centralize lifecycle in a base test case or trait. Create proxies in setUp, tear them down in tearDown, and never leave proxies behind after a failure:
abstract class ToxiproxyTestCase extends TestCase
{
protected Toxiproxy $toxiproxy;
protected array $created = [];
protected function setUp(): void
{
parent::setUp();
$this->toxiproxy = new Toxiproxy(env('TOXIPROXY_URL', 'http://127.0.0.1:8474'));
}
protected function tearDown(): void
{
foreach ($this->created as $name) {
try {
$this->toxiproxy->delete($name);
} catch (\Throwable $e) {
// already gone or server unreachable
}
}
parent::tearDown();
}
protected function proxy(string $name, string $upstream, ?string $listen = null): Proxy
{
$proxy = $this->toxiproxy->create($name, $upstream, $listen);
$this->created[] = $name;
return $proxy;
}
}
Use one proxy per dependency, named per test class. Avoid a single shared proxy across a whole suite. Names like redis_retry_test, mysql_timeout_test, or http_fallback_test keep failures readable and prevent accidental cross-test toxic leakage.
Reuse proxies with reset() only when tests are serialized. POST /reset enables all proxies and removes all active toxics — convenient in setUp, but suite-wide and hostile to parallel test workers. For parallel runs, create and delete per test or per worker.
Point framework configs at the proxy before the connection is resolved. With Laravel, set the host/port in defineEnvironment / getEnvironmentSetUp, then purge pooled connections:
config([
'database.connections.redis.host' => '127.0.0.1',
'database.connections.redis.port' => 26379,
]);
DB::purge('redis');
Existing pooled connections bypass newly injected toxics; always purge or reconnect after changing toxics mid-test.
Pattern toxics by failure mode. Use latency for slow calls and retry/backoff assertions; bandwidth (KB/s) for large-transfer or streaming tests; timeout to close the connection after N ms; slicer to fragment packets; and proxy disablement for hard outages. For round-trip latency, add toxics on both downstream (server→client) and upstream (client→server) — a single latency toxic is one-directional.
Test the behavior, not Toxiproxy. Assert retry counts, fallback responses, cache writes, or circuit-breaker state. Toxiproxy introspection ($proxy->getToxics()) belongs in setup/debugging, not in assertions.
Expose a small fixture API in your suite. A Chaos::redis() helper that returns a configured proxy keeps test bodies short and ensures cleanup is always registered.
get() when you intentionally reuse one.$proxy->getListen() and configure the app accordingly. Never hardcode ports in parallel CI jobs.try/finally, PHPUnit’s tearDownAfterClass, or a shutdown hook; a crashed test that leaves proxies behind poisons every later run.ECONNRESET rather than a clean timeout; assert on the observed exception, not the expected one.mysql:3306), not 127.0.0.1, when running in Compose/CI services.curl -s http://127.0.0.1:8474/proxies | jq and /proxies/{name}/toxics show live state and are often faster than adding test logging.Toxiproxy calls through tests.How can I help you explore Laravel packages today?