W testach integracyjnych niekiedy musimy podłączyć się do zewnętrznych baz danych, serwisów http czy innych elementów systemu, które są poza nasza aplikacją. Zewnętrzne serwisy httpd możemy zamockować przy pomocy wiremocka, jest to znane i popularne narzędzie.

Inaczej jest z bazami danych sql i nosql, często przy bazach relacyjnych w testach integracyjnych uruchamiało się bazę typu H2 (https://www.h2database.com/), która ma wsparcie w Springu i Hibernate.

W przypadku baz nosql w testach integracyjnych wykorzystywało się lokalną instancję danę bazy, nie jest to najlepsze podejście bo:

  • na każdym środowisku dobrze mieć tą samą wersję danej bazy danych w tej samej konfiguracji
  • problemy z aktualizacjami sytemu
  • przed uruchomieniem testów należy bazę danych doprowadzić za każdym razem do takiego samego stanu

czyli reasumując mamy kilka małych problemów.

Z pomocą przychodzi tutaj projekt Testcontainers (https://www.testcontainers.org/), który pozwala na uruchamianie baz danych, systemów kolejkowych, innych elementów systemu z kodu Javy/Kotlina jako obrazów dockerowych. W przypadku testów Spocka warto zapoznać się z dokumentacją: https://www.testcontainers.org/quickstart/spock_quickstart/

Jest to bardzo praktyczne rozwiązanie bo:

  • możemy uruchomić obraz praktycznie dowolnego elementu systemu dla którego jest obraz dockerowy
  • za każdym uruchomieniem mamy czysty stan bez zbędnych śmieci
  • każdy test jest uruchamiany w takim samym środowisku i takich samych wersja, wystarczy wybierać obrazy w ustalonych wersjach

wada – pod Windowsem docker wymaga Windows Pro, chociaż dockera też można uruchomić na Windows home wykorzystując straszą wersję lub wirtualizację, pod linuxem i macos docker nie wymaga większego kombinowania.

Dobra koniec gadania, trochę kodu:

Na początek dodanie odpowiednich zależności do gradla w sekcji dependencies:

    implementation platform('org.testcontainers:testcontainers-bom:1.15.3')
    testImplementation('org.testcontainers:spock')
    testImplementation('org.testcontainers:mongodb')

Bazy nosql typu Mongo

https://www.testcontainers.org/modules/databases/mongodb/
Jest uruchamiany z jako trait z goovy, a przy pomocy adnotacji DynamicPropertySource następuje nadpisanie konfiguracji springa:

trait MongoDbConfiguration {

    final static MONGO_CONTAINER = DockerImageName.parse("mongo:4.0.10")
            .asCompatibleSubstituteFor("mongo")

    static MongoDBContainer mongoContainer

    @DynamicPropertySource
    static void mongoDbProperties(DynamicPropertyRegistry registry) {
        mongoContainer = new MongoDBContainer(MONGO_CONTAINER)
        mongoContainer.start()
        def replicaSetUrl = mongoContainer.getReplicaSetUrl()
        registry.add("spring.data.mongodb.uri", () -> replicaSetUrl)
    }
}

Bazy nosql typu Redis

Resis w testcontainers nie ma osobnej dokumentacji, jego uruchomienie odbywa się przy pomocy standartowego mechanizmu testcontainers. Podobnie jak mongo konfiguracja jest uruchamiana jako trait z goovy, a przy pomocy adnotacji DynamicPropertySource następuje nadpisanie konfiguracji hostu i portu. Przyczym warto pamiętać że w dockerze jest uruchamiany port 6379, a na zawnątrz kontenera jest widoczny pod losowym portem:

trait RedisConfiguration {

    final static REDIS_CONTAINER = DockerImageName.parse("redis:5.0.0")
            .asCompatibleSubstituteFor("redis")

    static GenericContainer redisContainer

    @DynamicPropertySource
    static void mongoDbProperties(DynamicPropertyRegistry registry) {

        redisContainer = new GenericContainer(REDIS_CONTAINER)
                .withExposedPorts(6379)
        redisContainer.start()

        String address = redisContainer.getHost()
        Integer port = redisContainer.getFirstMappedPort()

        registry.add("spring.redis.host", () -> address)
        registry.add("spring.redis.port", () -> port.toString())
    }
}

Problemy

Czasami można dostać w testach po twarzy błędęm typu: Could not connect to Ryuk at localhost należy wtedy:
– w docker destop przestawić z obsługi WSL 2 na WSL 1 lub odwrotnie
– wyczyścić dockera docker system prune
– wykonać z konsoli z prawami admina netcfg -d i zrestartować kompa, po restarcie spróbować jeszcze raz

Link do repo z testowym projektem: https://github.com/shadoq/FromZeroToFullDevHero/tree/master/kotlin.testcontainer