Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill testcontainers명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | testcontainers |
| description | | Use when this capability is needed. |
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:testcontainersfor comprehensive documentation.
junit with Mockito for fast isolated testsrest-assured without containers if API is mocked<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Database specific -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
testImplementation("org.testcontainers:mongodb")
@SpringBootTest
@Testcontainers
class MyIntegrationTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Test
void testWithDatabase() {
// Connection auto-configured
}
}
| Container | Maven Artifact | Connection Details |
|---|---|---|
| PostgreSQLContainer | postgresql | JDBC + R2DBC |
| MySQLContainer | mysql | JDBC + R2DBC |
| MariaDBContainer | mariadb | JDBC + R2DBC |
| MongoDBContainer | mongodb | MongoConnectionDetails |
| KafkaContainer | kafka | KafkaConnectionDetails |
| RedisContainer | - | RedisConnectionDetails |
| RabbitMQContainer | rabbitmq | RabbitConnectionDetails |
| ElasticsearchContainer | elasticsearch | ElasticsearchConnectionDetails |
| CassandraContainer | cassandra | CassandraConnectionDetails |
@Container
@ServiceConnection(name = "redis")
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@Testcontainers
class SharedContainerTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Test
void test1() { /* same container */ }
@Test
void test2() { /* same container */ }
}
@TestConfiguration(proxyBeanMethods = false)
class TestContainersConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16-alpine");
}
}
@SpringBootTest
@Import(TestContainersConfig.class)
class ManagedContainerTest {
// Container lifecycle managed by Spring
// Started before beans, stopped after beans
}
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withReuse(true);
Richiede in ~/.testcontainers.properties:
testcontainers.reuse.enable=true
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test")
.withInitScript("init.sql");
@Container
@ServiceConnection
static MongoDBContainer mongo =
new MongoDBContainer("mongo:7.0")
.withSharding();
@Container
@ServiceConnection
static MySQLContainer<?> mysql =
new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Container
@ServiceConnection
static KafkaContainer kafka =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"))
.withKraft();
@Container
@ServiceConnection
static RabbitMQContainer rabbitmq =
new RabbitMQContainer("rabbitmq:3.12-management")
.withExposedPorts(5672, 15672);
@Container
@ServiceConnection
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
Dedicated skills: For comprehensive messaging test coverage, see
messaging-testing-kafka,messaging-testing-rabbitmq, andmessaging-testing.
@SpringBootTest
@Testcontainers
class KafkaProduceConsumeTest {
@Container
@ServiceConnection
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("apache/kafka-native:3.8.0"));
@Autowired
private KafkaTemplate<String, OrderEvent> kafkaTemplate;
@Autowired
private OrderRepository orderRepository;
@Test
void shouldProcessOrderViaKafka() throws Exception {
kafkaTemplate.send("orders", "key-1",
new OrderEvent("123", "CREATED")).get(10, TimeUnit.SECONDS);
await().atMost(Duration.ofSeconds(10))
.untilAsserted(() ->
assertThat(orderRepository.findById("123")).isPresent());
}
}
@SpringBootTest
@Testcontainers
class RabbitProduceConsumeTest {
@Container
@ServiceConnection
static RabbitMQContainer rabbit = new RabbitMQContainer("rabbitmq:3.13-management");
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderRepository orderRepository;
@Test
void shouldProcessOrderViaRabbit() {
rabbitTemplate.convertAndSend("orders.exchange", "orders.created",
new OrderEvent("456", "CREATED"));
await().atMost(Duration.ofSeconds(10))
.untilAsserted(() ->
assertThat(orderRepository.findById("456")).isPresent());
}
}
@SpringBootTest
@Testcontainers
class RedisPubSubTest {
@Container
@ServiceConnection(name = "redis")
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine").withExposedPorts(6379);
@Autowired
private StringRedisTemplate redisTemplate;
@Test
void shouldPublishAndReceiveMessage() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
List<String> received = new CopyOnWriteArrayList<>();
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisTemplate.getConnectionFactory());
container.addMessageListener((message, pattern) -> {
received.add(new String(message.getBody()));
latch.countDown();
}, new ChannelTopic("orders"));
container.afterPropertiesSet();
container.start();
redisTemplate.convertAndSend("orders", "{\"orderId\":\"789\"}");
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
assertThat(received.get(0)).contains("789");
container.stop();
}
}
@Testcontainers
@SpringBootTest
class LegacyTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
@Testcontainers
class NetworkTest {
static Network network = Network.newNetwork();
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16")
.withNetwork(network)
.withNetworkAliases("postgres");
@Container
static GenericContainer<?> app =
new GenericContainer<>("myapp:latest")
.withNetwork(network)
.dependsOn(postgres)
.withEnv("DATABASE_HOST", "postgres");
}
@Testcontainers
class ComposeTest {
@Container
static DockerComposeContainer<?> compose =
new DockerComposeContainer<>(new File("docker-compose-test.yml"))
.withExposedService("postgres", 5432)
.withExposedService("redis", 6379);
@Test
void test() {
String host = compose.getServiceHost("postgres", 5432);
int port = compose.getServicePort("postgres", 5432);
}
}
new GenericContainer<>("custom-image")
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
.waitingFor(Wait.forLogMessage(".*Started.*", 1))
.waitingFor(Wait.forListeningPort())
.withStartupTimeout(Duration.ofMinutes(2));
| Do | Don't |
|---|---|
Use static containers | Create container per test method |
Use @ServiceConnection | Manual property configuration |
| Use Spring Bean lifecycle | JUnit lifecycle for app-dependent containers |
| Enable container reuse in dev | Start fresh containers every run |
| Use specific image tags | Use latest tag |
| Share containers via base class | Duplicate container declarations |
// Check Docker is running
// Check image exists
// Increase startup timeout
.withStartupTimeout(Duration.ofMinutes(5))
// Always use exposed port mapping
container.getMappedPort(5432)
// Never hardcode ports
// Enable reuse
.withReuse(true)
// Use lighter images (-alpine)
new PostgreSQLContainer<>("postgres:16-alpine")
| Anti-Pattern | Why It's Bad | Solution |
|---|---|---|
| Creating container per test method | Extremely slow | Use static containers shared across tests |
| Not using @ServiceConnection | Manual config duplication | Let Spring auto-configure from container |
| Using latest tag | Non-deterministic tests | Pin specific version (postgres:16-alpine) |
| No withReuse for local dev | Slow dev feedback loop | Enable reuse in ~/.testcontainers.properties |
| Hardcoded ports | Port conflicts | Use getMappedPort() for dynamic ports |
| Ignoring startup timeout | Tests hang | Set withStartupTimeout appropriately |
| Not cleaning up test data | Tests interfere with each other | Use @Transactional or manual cleanup |
| Problem | Likely Cause | Solution |
|---|---|---|
| "Could not find image" | Docker not running or image unavailable | Start Docker, check image name |
| Container startup timeout | Image too large or slow startup | Increase timeout, use lighter images |
| Port already in use | Previous test didn't clean up | Use dynamic ports with getMappedPort() |
| Tests very slow | Starting containers every test | Use static containers |
| Connection refused | Using localhost instead of container host | Use container.getHost() and getMappedPort() |
| "@ServiceConnection not working" | Wrong Spring Boot version | Requires Spring Boot 3.1+, check version |
Source: claude-dev-suite/claude-dev-suite — distributed by TomeVault.