RESTful APIs and HTTP Clients
https://guides.micronaut.io/latest/micronaut-http-client-gradle-kotlin.htm
The following example can be used in testing RESTful APIs and HTTP Clients.
Overview
We have created a Micronaut application written in Kotlin to consume the GitHub API with the Micronaut HTTP Client.
In particular, we consume the List releases endpoint, which returns a list of releases. This API resource can be consumed by both authenticated and anonymous clients.
The Test Code
src/itest/kotlin/example/micronaut/GithubControllerTest.kt
package example.micronaut import io.micronaut.core.type.Argument import io.micronaut.http.HttpRequest import io.micronaut.http.HttpStatus import io.micronaut.http.client.HttpClient import io.micronaut.http.client.annotation.Client import io.micronaut.test.extensions.junit5.annotation.MicronautTest import jakarta.inject.Inject import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @MicronautTest class GithubControllerTest { @Inject @Client("/") lateinit var client: HttpClient @Test fun verifyGithubReleasesCanBeFetchedWithLowLevelHttpClient() { //when: val request: HttpRequest<Any> = HttpRequest.GET("/github/releases-lowlevel") val rsp = client.toBlocking().exchange(request, Argument.listOf(GithubRelease::class.java)) //then: 'the endpoint can be accessed' assertEquals(HttpStatus.OK, rsp.status) assertNotNull(rsp.body()) //when: val releases = rsp.body() //then: assertNotNull(releases) val regex = Regex("Micronaut( Framework)? [0-9].[0-9].[0-9]([0-9])?( (RC|M)[0-9])?") for (release in releases) { assertTrue(regex.matches(release.name)) } } @Test fun verifyGithubReleasesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() { //when: val request: HttpRequest<Any> = HttpRequest.GET("/github/releases-lowlevel") val githubReleases = client.toBlocking().retrieve(request, Argument.listOf(GithubRelease::class.java)) //then: val regex = Regex("Micronaut( Framework)? [0-9].[0-9].[0-9]([0-9])?( (RC|M)[0-9])?") for (release in githubReleases) { assertTrue(regex.matches(release.name)) } } }
Database / Repository
The following example(s) can be used for tests involving database connection.
// TODO: complete the flow
PostgreSQL
https://akobor.me/posts/using-testcontainers-with-micronaut-and-kotest
Add Testcontainers as a testImplementation dependency the build.gradle file:
// TODO: check if version is correct testImplementation("org.testcontainers:postgresql:1.17.3")
https://ruuben.medium.com/creating-a-native-image-with-micronaut-2-and-postgresql-3574a6d52946
Update application-test.yml:
datasources: default: url: jdbc:tc:postgresql://localhost/test driverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver username: test password: test
Instead of using FlyWay migration for initializing and clearing the DB, we can choose if the image should be reused in all the tests, or restart it in each of them in the same configuration file:
testcontainers: reuse: enabled: false
Here’s a sample test code from https://akobor.me/posts/using-testcontainers-with-micronaut-and-kotest :
Alternative approach:
@MicronautTest class ExampleTest(private val repository: Repository) : DatabaseStringSpec({ "when we insert something into our database, it should exist" { repository.insert(value = "something") // We insert something in our database repository.findByValue(value = "something") shouldNotBe null // And we expect that the inserted thing is actually there } "when we don't call insert, the table should be empty" { repository.findByValue(value = "something") shouldBe null // We expect that there is nothing in the database yet } })
https://exceptionly.com/2022/04/09/database-and-kafka-integration-testcontainers-spring-boot/
GenericContainer kafkaContainer = new GenericContainer("bitnami/kafka:latest") .withExposedPorts(9092) .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("testcontainers.kafka"))) .withCreateContainerCmdModifier((Consumer<CreateContainerCmd>) cmd -> cmd.withHostConfig( new HostConfig().withPortBindings( new PortBinding(Binding.bindPort(9092), new ExposedPort(9092))) )) .withEnv("KAFKA_ADVERTISED_HOSTNAME", localHostAddress()) .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "true") .withEnv("KAFKA_GROUP_MAX_SESSION_TIMEOUT_MS", "1000000") .withEnv("KAFKA_CREATE_TOPICS", "dev.exceptionly.topic1,dev.exceptionly.topic2") .withCommand("run", "-e", "{"kafka_advertised_hostname":" + localHostAddress() + "}") .waitingFor(Wait.forLogMessage("Created topic .*", 3) .withStartupTimeout(Duration.ofSeconds(180))) .withReuse(true);
Message Broker
The following example(s) can be used for integration testing that connects to a message broker.
Kafka
// TODO: look for other approach/references
https://exceptionly.com/2022/04/09/database-and-kafka-integration-testcontainers-spring-boot/
GenericContainer postgresDBContainer = new GenericContainer("postgres:latest") .withCreateContainerCmdModifier((Consumer<CreateContainerCmd>) cmd -> cmd.withHostConfig( new HostConfig().withPortBindings(new PortBinding(Binding.bindPort(5432), new ExposedPort(5432))) )) .withExposedPorts(5432) .withEnv("POSTGRES_PASSWORD", "<postgres_username>") .withEnv("POSTGRES_USER", "<postgres_password>") .withEnv("LANG", "en_US.utf8") .withReuse(true);