Testcontainers module for Shopify's Toxiproxy TCP proxy that simulates network failure conditions for resilience testing
npx @tessl/cli install tessl/maven-org-testcontainers--toxiproxy@1.21.0Testcontainers module for Shopify's Toxiproxy TCP proxy that simulates network failure conditions for resilience testing. This module enables developers to test application resilience by introducing network toxics (failures) between containers or between test code and containers in isolated test environments.
Package Name: org.testcontainers:toxiproxy
Package Type: Maven
Language: Java
Installation:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>toxiproxy</artifactId>
<version>1.21.3</version>
<scope>test</scope>
</dependency>Gradle:
testImplementation 'org.testcontainers:toxiproxy:1.21.3'import org.testcontainers.containers.ToxiproxyContainer;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.Proxy;
import eu.rekawek.toxiproxy.model.ToxicDirection;import org.testcontainers.containers.ToxiproxyContainer;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.Proxy;
import eu.rekawek.toxiproxy.model.ToxicDirection;
// Create a common docker network
Network network = Network.newNetwork();
// Target container (could be any service)
GenericContainer<?> redis = new GenericContainer<>("redis:6-alpine")
.withExposedPorts(6379)
.withNetwork(network)
.withNetworkAliases("redis");
// Toxiproxy container on same network
ToxiproxyContainer toxiproxy = new ToxiproxyContainer("ghcr.io/shopify/toxiproxy:2.5.0")
.withNetwork(network);
// Start containers
redis.start();
toxiproxy.start();
// Create Toxiproxy client and proxy
ToxiproxyClient client = new ToxiproxyClient(
toxiproxy.getHost(),
toxiproxy.getControlPort()
);
Proxy proxy = client.createProxy("redis", "0.0.0.0:8666", "redis:6379");
// Connect to service via proxy
String proxyHost = toxiproxy.getHost();
int proxyPort = toxiproxy.getMappedPort(8666);
// Apply network toxics for testing
proxy.toxics().latency("latency", ToxicDirection.DOWNSTREAM, 1000);
proxy.toxics().bandwidth("slow_connection", ToxicDirection.UPSTREAM, 100);The Toxiproxy module is built around these key components:
Create and manage the Toxiproxy Docker container with automatic port exposure and lifecycle management.
/**
* Testcontainers implementation for Toxiproxy.
* Supported images: ghcr.io/shopify/toxiproxy, shopify/toxiproxy
* Exposed ports: HTTP: 8474, Proxied Ports: 8666-8697
*/
public class ToxiproxyContainer extends GenericContainer<ToxiproxyContainer> {
/**
* @deprecated use ToxiproxyContainer(DockerImageName) instead
*/
@Deprecated
public ToxiproxyContainer();
public ToxiproxyContainer(String dockerImageName);
public ToxiproxyContainer(DockerImageName dockerImageName);
/**
* @return Publicly exposed Toxiproxy HTTP API control port
*/
public int getControlPort();
}Usage Examples:
// Using default image
ToxiproxyContainer toxiproxy = new ToxiproxyContainer();
// Using specific image version
ToxiproxyContainer toxiproxy = new ToxiproxyContainer("ghcr.io/shopify/toxiproxy:2.5.0");
// Using DockerImageName
DockerImageName imageName = DockerImageName.parse("shopify/toxiproxy:2.1.0");
ToxiproxyContainer toxiproxy = new ToxiproxyContainer(imageName);
// Get control port for client creation
int controlPort = toxiproxy.getControlPort();Create TCP proxies between services and apply network failure conditions using the ToxiproxyClient.
/**
* Client for managing Toxiproxy proxies via HTTP API
*/
public class ToxiproxyClient {
public ToxiproxyClient(String host, int port);
/**
* Create a new proxy
* @param name proxy name (must be unique)
* @param listen listen address in format "host:port"
* @param upstream upstream address in format "host:port"
* @return Proxy instance for managing toxics
*/
public Proxy createProxy(String name, String listen, String upstream) throws IOException;
}
/**
* Represents a single proxy connection
*/
public class Proxy {
/**
* @return proxy name
*/
public String getName();
/**
* @return ToxicList for managing network failure conditions
*/
public ToxicList toxics();
}Usage Examples:
// Create client
ToxiproxyClient client = new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());
// Create proxy for Redis container
Proxy redisProxy = client.createProxy("redis", "0.0.0.0:8666", "redis:6379");
// Create proxy for external service
Proxy apiProxy = client.createProxy("api", "0.0.0.0:8667", "api.example.com:80");
// Get proxy details
String proxyName = redisProxy.getName(); // "redis"Apply various network failure conditions to test application resilience.
/**
* Interface for managing toxic conditions on a proxy
*/
public interface ToxicList {
/**
* Limit connection bandwidth
* @param name toxic name
* @param direction traffic direction (UPSTREAM/DOWNSTREAM)
* @param rate maximum kilobytes per second
* @return Toxic instance
*/
Toxic bandwidth(String name, ToxicDirection direction, long rate);
/**
* Add latency with optional jitter
* @param name toxic name
* @param direction traffic direction
* @param latency latency in milliseconds
* @return Toxic instance for further configuration
*/
Toxic latency(String name, ToxicDirection direction, long latency);
/**
* Slice TCP data into small packets
* @param name toxic name
* @param direction traffic direction
* @param averageSize average packet size in bytes
* @param sizeVariation size variation in bytes
* @param delay delay between packets in microseconds
* @return Toxic instance
*/
Toxic slicer(String name, ToxicDirection direction, int averageSize, int sizeVariation, int delay);
/**
* Delay socket closing
* @param name toxic name
* @param direction traffic direction
* @param delay delay in milliseconds
* @return Toxic instance
*/
Toxic slowClose(String name, ToxicDirection direction, long delay);
/**
* Stop data transmission and close connection after timeout
* @param name toxic name
* @param direction traffic direction
* @param timeout timeout in milliseconds (0 = never close)
* @return Toxic instance
*/
Toxic timeout(String name, ToxicDirection direction, long timeout);
/**
* Close connection when data limit exceeded
* @param name toxic name
* @param direction traffic direction
* @param bytes data limit in bytes
* @return Toxic instance
*/
Toxic limitData(String name, ToxicDirection direction, long bytes);
/**
* Get existing toxic by name
* @param name toxic name
* @return Toxic instance
*/
Toxic get(String name);
}
/**
* Represents an individual toxic condition
*/
public interface Toxic {
/**
* Remove this toxic
*/
void remove() throws IOException;
/**
* Set jitter for latency toxics
* @param jitter jitter in milliseconds
* @return this toxic for chaining
*/
Toxic setJitter(int jitter);
}
/**
* Traffic direction for toxic application
*/
public enum ToxicDirection {
UPSTREAM, // Client to server
DOWNSTREAM // Server to client
}Usage Examples:
// Add 1 second latency with 100ms jitter to downstream traffic
proxy.toxics()
.latency("latency", ToxicDirection.DOWNSTREAM, 1000)
.setJitter(100);
// Limit bandwidth to 100 KB/s upstream
proxy.toxics().bandwidth("slow_upload", ToxicDirection.UPSTREAM, 100);
// Completely cut connection (0 bandwidth both directions)
proxy.toxics().bandwidth("cut_down", ToxicDirection.DOWNSTREAM, 0);
proxy.toxics().bandwidth("cut_up", ToxicDirection.UPSTREAM, 0);
// Add connection timeout after 5 seconds
proxy.toxics().timeout("connection_timeout", ToxicDirection.DOWNSTREAM, 5000);
// Slice packets into 32-byte chunks with 10ms delay
proxy.toxics().slicer("packet_slicer", ToxicDirection.DOWNSTREAM, 32, 8, 10000);
// Remove a toxic
proxy.toxics().get("latency").remove();Legacy convenience methods for proxy creation and management. These methods are deprecated and will be removed in future versions.
public class ToxiproxyContainer extends GenericContainer<ToxiproxyContainer> {
/**
* @deprecated ToxiproxyContainer will not build the client. Proxies should be provided manually.
*/
@Deprecated
public ContainerProxy getProxy(GenericContainer<?> container, int port);
/**
* @deprecated ToxiproxyContainer will not build the client. Proxies should be provided manually.
*/
@Deprecated
public ContainerProxy getProxy(String hostname, int port);
/**
* @deprecated Legacy proxy wrapper class
*/
@Deprecated
public static class ContainerProxy {
/**
* The IP address that this proxy container may be reached on from the host machine
*/
public String getContainerIpAddress();
/**
* The mapped port of this proxy. This is a port of the host machine
*/
public int getProxyPort();
/**
* The original (exposed) port of this proxy. This is a port of the Toxiproxy Docker container
*/
public int getOriginalProxyPort();
public String getName();
public ToxicList toxics();
/**
* Cuts the connection by setting bandwidth in both directions to zero
* @param shouldCutConnection true to cut connection, false to restore
*/
public void setConnectionCut(boolean shouldCutConnection);
}
}The module may throw these exceptions during operation:
/**
* Common exceptions
*/
IOException // Thrown during proxy creation or toxic manipulation
IllegalStateException // Thrown when maximum proxy limit (32) exceeded
RuntimeException // Wrapper for IOException in proxy operationsError Handling Examples:
try {
Proxy proxy = client.createProxy("service", "0.0.0.0:8666", "service:8080");
proxy.toxics().latency("test_latency", ToxicDirection.DOWNSTREAM, 1000);
} catch (IOException e) {
// Handle proxy creation or toxic application failure
System.err.println("Failed to configure proxy: " + e.getMessage());
}
try {
// This will fail if more than 32 proxies are created
ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy("host", 8080);
} catch (IllegalStateException e) {
System.err.println("Maximum number of proxies exceeded");
}