ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • redis mongodb testcontainer example 1편
    스프링 2023. 7. 20. 10:17

    https://github.com/TaeWoonJeong/MongoRedisTestContainer

     

    GitHub - TaeWoonJeong/MongoRedisTestContainer

    Contribute to TaeWoonJeong/MongoRedisTestContainer development by creating an account on GitHub.

    github.com

    클론하고, MongoRedisSingleTestContainer 모듈에 가면 된다.

     

    single이므로 redisconfig가 없어도 자동으로 localhost와 6379포트로 설정 되지만, 그래도 RedisConfig를 넣어줬다.

    @Configuration
    @EnableRedisRepositories
    class RedisConfig(
        @Value("\${spring.data.redis.host}")
        private val redisHost: String,
        @Value("\${spring.data.redis.port}")
        private val redisPort: Int
    ) {
        @Bean
        fun redisConnectionFactory() = LettuceConnectionFactory(redisHost, redisPort)
    }

    나머지 코드들은 간단하게 redis와 mongo에 값 추가와 조회기능이 있다.

     

    embedded redis가 2020년이 마지막 커밋이어서, testcontainer로 한다.

    위에서 RedisConfig파일에 @Value로 application.yml의 값을 가져다 사용하기 때문에,

    @SpringBootTest를 사용하는 테스트코드에서도 application.yml 값을 넣어줘야한다.

    없으면, 에러가 난다.

     

    물론 RedisConfig파일을 삭제하고 application.yml 들을 삭제하면, 정상작동한다.

     

     

    테스트코드 설명

    @SpringBootTest
    class CommonDependencyConfig {
        @Autowired
        lateinit var userRedisRepository: UserRedisRepository
    
        @Autowired
        lateinit var userMongoRepository: UserMongoRepository
    
        @Autowired
        lateinit var dbService: DBService
    }

    addMongoTest, getMongoTest, addRedisTest, getRedisTest에서 필요한 위존성들을 위에서 한번에 autowired해준다.

    @Configuration
    @Testcontainers
    class TestContainerMongoConfig : CommonDependencyConfig() {
        companion object {
            private const val MONGO_DOCKER_IMAGE = "mongo:latest"
    
            @Container
            val MONGO_CONTAINER = GenericContainer(
                DockerImageName.parse(MONGO_DOCKER_IMAGE)
            ).withExposedPorts(27017)
                .withReuse(true)
    
            init {
                MONGO_CONTAINER.start()
            }
        }
    }
    @Configuration
    @Testcontainers
    class TestContainerRedisConfig : CommonDependencyConfig() {
        companion object {
            private const val REDIS_DOCKER_IMAGE = "redis:latest"
    
            @Container
            val REDIS_CONTAINER = GenericContainer(
                DockerImageName.parse(REDIS_DOCKER_IMAGE)
            ).withExposedPorts(6379)
                .withReuse(true)
    
            init {
                REDIS_CONTAINER.start()
            }
        }
    }

    CommonDependencyConfig를 상속받고, 각자 컨테이너들을 시작해준다.

    @TestContainers 어노테이션이 없으면, 한번 만든 컨테이너에서 계속 실험하므로, 결과가 틀리게 된다.

    @TestContianers 어노테이션을 붙이면, 컨테이너가 테스트마다(클래스마다) 재생성되므로, 만든다음, 아래의 dynamicPropertySource로 매핑해줘서 작동한다.

    class AddRedisTest : TestContainerRedisConfig() {
        companion object {
            @JvmStatic
            @DynamicPropertySource
            fun overrideProps(registry: DynamicPropertyRegistry) {
                registry.add("spring.data.redis.host") { REDIS_CONTAINER.host }
                registry.add("spring.data.redis.port") { REDIS_CONTAINER.getMappedPort(6379) }
    //            registry.add("spring.data.redis.port") { REDIS_CONTAINER.firstMappedPort }
            }
        }
    
        @BeforeEach
        fun setup() {
            val address: String = REDIS_CONTAINER.host
            val port: Int = REDIS_CONTAINER.firstMappedPort
            println(address + port + "입니다.")
        }
    
        @Test
        fun addRedisTest() {
            dbService.addRedis("email", "name")
            val redis = dbService.getRedis()
            Assertions.assertEquals(redis.size, 1)
        }
    }

    addRedisTest를 예를 들면, Redis를 사용하므로, 위에서 생성한 컨테이너 설정을 상속받아주고, 

    @DynamicPropertySource로 port와 host를 설정해준다.

    이때 @JvmStatic을 넣어야하는데, 없으면 application.yml에서 설정한 포트로 간다.

    즉 컨테이너를 만드는데, 컨테이너에서 테스트하지 않고, 로컬에서 테스트가 된다.

    MongoDB테스트도 똑같은데, 역시 @JvmStatic이 없으면, 로컬 몽고디비에 테스트를 하므로 실패한다.

     

    https://velog.io/@suev72/JvmStatic%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C

     

    @JvmStatic가 무엇일까?

    안드로이드 New->Fragment 로 프래그먼트를 생성해보면, 이렇게 newInstance함수를 자동으로 만들어주는데, @JvmStatic이라는 어노테이션을 볼 수 있다. @JvmStatic가 뭘까? > 결론: Java의 static 처럼 쓰기 위함

    velog.io

    추측하자면, companion object로 설정을 해주면, 밖에서 설정을 읽지 못해서, JvmStatic을 붙여줘야 컨테이너에 테스트를 시키는거 같다.

     

    아쉬운점은, 만약

    class AddMongoTest : TestContainerMongoConfig() {
        companion object {
            @JvmStatic
            @DynamicPropertySource
            fun overrideProps(registry: DynamicPropertyRegistry) {
                registry.add("spring.data.mongodb.host") { MONGO_CONTAINER.host }
    //            registry.add("spring.data.mongodb.port") { MONGO_CONTAINER.getMappedPort(27017).toString() }
                registry.add("spring.data.mongodb.port") { MONGO_CONTAINER.firstMappedPort.toString() }
            }
        }
    
        @BeforeEach
        fun setup() {
            val address: String = MONGO_CONTAINER.host
            val port: Int = MONGO_CONTAINER.firstMappedPort
            println(address + port + "입니다.")
        }
    
        @Test
        fun addMongoTest() {
            dbService.addMongo("email", "name")
            val mongo = dbService.getMongo()
            Assertions.assertEquals(mongo.size, 1)
        }
        
        @Test
        fun addMongoTest2() {
            dbService.addMongo("email", "name")
            val mongo = dbService.getMongo()
            Assertions.assertEquals(mongo.size, 1)
        }
    }

    이렇게 만든 경우, 하나의 컨테이너에서 2번 테스트하는게 되어서, 둘 중 하나의 테스트는 실패한다.

     

    이부분 해결책을 찾아보겠다.

     

     

    다음은, 레디스 레플리케이션과 몽고디비 레플리케이션 설정을 하고, 테스트컨테이너로 테스트 해보겠다.

    '스프링' 카테고리의 다른 글

    코틀린에서 log 사용하기  (0) 2023.07.04
Designed by Tistory.