Tech

Test Containers for Spring Boot

syaku 2023. 10. 13. 18:53
반응형

Test containers for Redis

설치

ext.testcontainersVersion = '1.18.3'

implementation "org.springframework.boot:spring-boot-starter-data-redis"
implementation 'org.apache.commons:commons-pool2:2.11.1'

testImplementation "org.springframework.cloud:spring-cloud-starter-bootstrap"
implementation platform("org.testcontainers:testcontainers-bom:${testcontainersVersion}")
testImplementation 'org.testcontainers:testcontainers'

테스트 컨테이너 초기화

public class RedisTestContainerInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final int REDIS_PORT = 6379;

    private static final GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7.0.3-alpine"))
        .withExposedPorts(REDIS_PORT)
        .waitingFor(Wait.forListeningPort());

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        redis.start();

        Map<String, Object> properties = new HashMap<>();
        properties.put("spring.redis.host", redis.getHost());
        properties.put("spring.redis.port", redis.getMappedPort(REDIS_PORT));

        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        propertySources.addFirst(new MapPropertySource("redis-testcontainer", properties));
    }
}

스프링 설정

spring:
	redis:
	  host: localhost
	  port: 6379
	  lettuce:
	    pool:
	      max-active: 10
	      max-idle: 10
	      min-idle: 2

스프링 테스트 코드

@Slf4j
@SpringBootTest
@ContextConfiguration(initializers = RedisTestContainerInitializer.class)
@AutoConfigureMockMvc
class AccessTokenRestControllerTest {
}

Test containers for kafka

설치

ext.testcontainersVersion = '1.18.3'

testImplementation "org.springframework.kafka:spring-kafka-test"

testImplementation "org.springframework.cloud:spring-cloud-starter-bootstrap"
implementation platform("org.testcontainers:testcontainers-bom:${testcontainersVersion}")
testImplementation 'org.testcontainers:kafka'

테스트 컨테이너 초기화

public class KafkaTestContainerInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    private static final KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest"));
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
		kafka.start();

        Map<String, Object> properties = new HashMap<>();
        properties.put("spring.kafka.bootstrap-servers", kafka.getBootstrapServers());
        properties.put("spring.kafka.consumer.bootstrap-servers", kafka.getBootstrapServers());
        properties.put("spring.kafka.producer.bootstrap-servers", kafka.getBootstrapServers());

        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        propertySources.addFirst(new MapPropertySource("kafka-testcontainer", properties));
    }
}

스프링 설정

spring:
	kafka:
    bootstrap-servers: localhost:9092
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

스프링 테스트 코드

@Slf4j
@SpringBootTest
@ContextConfiguration(initializers = KafkaTestContainerInitializer.class)
class ParkingKafkaProducerTest {
    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private KafkaProperties kafkaProperties;

    @Autowired
    private ParkingKafkaProducer parkingKafkaProducer;

    @Value("${parking.kafka.parking-topic}")
    private String parkingTopic;

    private Consumer<String, String> createConsumer() {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getConsumer().getBootstrapServers());
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        return new KafkaConsumer<>(properties);
    }

    @Test
    void send() throws Exception {
        ParkingDto dto = ParkingDto.builder()
            .enteredAt(LocalDateTime.now())
            .vehicleNumber("11가2222")
            .entryTid(UUIDGenerator.of())
            .build();

        parkingKafkaProducer.send(dto);

        try (Consumer<String, String> consumer = createConsumer()) {
            consumer.subscribe(Collections.singleton(parkingTopic));
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));

            assertFalse(records.isEmpty());

            ConsumerRecord<String, String> received = records.iterator().next();
            assertEquals(objectMapper.writeValueAsString(dto), received.value());

            log.trace("{}", received.value());
        }

    }

}

[번외] 테스트를 일반적으로 무시하고 필요에 따라 직접 실행하기

// 테스트 코드에 선언
@EnabledIf(expression = "#{environment['spring.profiles.active']?.contains('docker')}", loadContext = true)

// 테스트 실행 설정에서 VM Options 를 추가한다.
-Dspring.profiles.active=test,docker

// grable 테스트 설정 추가한다.
test {
    doFirst {
        def profiles = System.getProperty('spring.profiles.active') ?: 'test'
        systemProperty 'spring.profiles.active', profiles
    }
}

참고

반응형