SaFi Bank Space : Unit Test: Sample Codes (WIP)

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);