DEV Community

Vuong Dang
Vuong Dang

Posted on • Originally published at vuongdang.dev

2

Database Testing with Testcontainers and Kotlin Exposed ORM

In this article, we will explore how to use Testcontainers
and Exposed, a lightweight ORM framework for Kotlin, to create a controlled environment for testing MySQL Database operations.

Setup a Kotlin project with gradle

First we install the latest gradle version using SDKMAN

sdk install gradle 8.1.1
Enter fullscreen mode Exit fullscreen mode

Then create a new Gradle project using gradle init with Kotlin support

mkdir kotlin-project
cd kotlin-project
gradle init --type kotlin-application --dsl kotlin
Enter fullscreen mode Exit fullscreen mode

Add dependencies to gradle (build.gradle.kts)

  // Exposed
  implementation("org.jetbrains.exposed", "exposed-core", "0.40.1")
  implementation("org.jetbrains.exposed", "exposed-dao", "0.40.1")
  implementation("org.jetbrains.exposed", "exposed-jdbc", "0.40.1")
  // MySQL JDBC Driver
  implementation("mysql:mysql-connector-java:8.0.19")

  // Connection pool
  implementation("com.zaxxer:HikariCP:5.0.1")

  // Test containers
  testImplementation("org.testcontainers:mysql:1.18.3")
Enter fullscreen mode Exit fullscreen mode

Define the entity using Exposed DAO flavor

Exposed supports DSL (Domain Specific Language) and DAO (Data Access Object).
The DAO flavor in Exposed is a lightweight ORM for performing CRUD operations on entities 1.

object Users: LongIdTable() {
    val name = varchar("name", 50)
}

class User(id: EntityID<Long>) : LongEntity(id) {
    companion object : LongEntityClass<User>(Users)

    var name by Users.name
}
Enter fullscreen mode Exit fullscreen mode

LongIdTable uses an auto-incrementing Long primary key

Use Testcontainers to start MySQL container, and perform CRUD operations on User entity

We use a single test container to improve test speed by avoiding repetitive container startup and shutdown for every test or test-class

object TestDatabase {

    private val mySQLContainer: MySQLContainer<Nothing> = MySQLContainer<Nothing>("mysql:8.0.26").apply {
        withDatabaseName("test-db")
        withUsername("test-user")
        withPassword("test-password")
        start() // Start the container
    }

    init {
        val config = HikariConfig().apply {
            jdbcUrl = mySQLContainer.jdbcUrl
            username = mySQLContainer.username
            password = mySQLContainer.password
            driverClassName = "com.mysql.cj.jdbc.Driver"
            maximumPoolSize = 10
        }
        val dataSource = HikariDataSource(config)

        // This doesn't connect to the database but provides a descriptor for future use
        // In the main app, we would do this on system start up
        // https://github.com/JetBrains/Exposed/wiki/Database-and-DataSource
        Database.connect(dataSource)

        // Create the schema
        transaction {
            SchemaUtils.create(Users)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We create a simple test to test the crud operations.

Note: To run the tests on your local machine, you must have Docker installed and running.
If you're using a Mac, you can use Docker Desktop for Mac.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserRepositoryTest {

    @BeforeAll
    fun setUp() {
        // Use the TestDatabase singleton to initialize the database
        TestDatabase
    }

    @Test
    fun crudUser() {
        transaction {
            // Create
            val newUser = User.new {
                name = "Alice"
            }

            // Read
            val retrievedUser = User.findById(newUser.id)
            assertEquals("Alice", retrievedUser?.name)

            // Update
            retrievedUser?.apply {
                name = "Bob"
            }

            val updatedUser = User.findById(newUser.id)
            assertEquals("Bob", updatedUser?.name)

            // Delete
            updatedUser?.delete()
            val deletedUser = User.findById(newUser.id)
            assertNull(deletedUser)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

We've explored how to efficiently set up and automate Kotlin database testing using Testcontainers and the Exposed framework.

  • Testcontainers allow us to run isolated database instance in Docker containers
  • Exposed, with its lightweight DAO, make it easy to perform CRUD operations.
  • Using a single test container throughout our test suit significantly improves performance.

You can find the complete source code for this tutorial on GitHub

Further Reading

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay