Skip to content

Commit

Permalink
Create integration test cases.
Browse files Browse the repository at this point in the history
  • Loading branch information
driessamyn committed Nov 7, 2024
1 parent 5ccd1ef commit 37f25d2
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 14 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ Here's a simple example of how to use Kapper:
val connection: Connection = ...

// Execute a SQL query and map the results to a list of User objects
val users: List<User> = connection.query("SELECT * FROM users", DynamicParameters())
val users: List<User> = connection.query<User>("SELECT * FROM users")

// Execute a SQL statement with parameters
val updatedRows = connection.execute(
"UPDATE users SET name = ? WHERE id = ?",
DynamicParameters("John Doe" to "name", 123 to "id")
"UPDATE users SET name = :name WHERE id = :id",
mapOf("name" to "John Doe", "id" to 123)
)

// Query a single result and map it to a User object
val user: User = connection.querySingle(
"SELECT * FROM users WHERE id = ?",
DynamicParameters(1 to "id")
val user: User? = connection.querySingle<User>(
"SELECT * FROM users WHERE id = :id",
mapOf("id" to 1)
)
```

Expand Down
14 changes: 13 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@

[versions]
junit-jupiter = "5.11.3"
kotest = "6.0.0.M1"
mockito = "5.14.2"
mockk = "1.13.13"
mysql-driver = "8.0.33"
postgresql-driver = "42.7.4"
test-conatainers = "1.20.3"

[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
# mockito only to be used from Java code (API usability test)
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mysql-driver = { module = "com.mysql:mysql-connector-j", version.ref = "mysql-driver" }
postgresql-driver = { module = "org.postgresql:postgresql", version.ref = "postgresql-driver" }
test-cotainers-junit = { module = "org.testcontainers:junit-jupiter", version.ref = "test-conatainers" }
test-containers-junit-mysql = { module = "org.testcontainers:mysql", version.ref = "test-conatainers" }
test-containers-junit-postgresql = { module = "org.testcontainers:postgresql", version.ref = "test-conatainers" }

[bundles]
test = ["junit-jupiter", "mockk"]
test = ["junit-jupiter", "mockk", "kotest-assertions-core"]
test-containers = ["test-cotainers-junit", "test-containers-junit-mysql", "test-containers-junit-postgresql"]
test-dbs = ["mysql-driver", "postgresql-driver"]

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.0.21" }
Expand Down
43 changes: 36 additions & 7 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.kotlin.dsl.test

plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
alias(libs.plugins.kotlin.jvm)
Expand All @@ -13,29 +15,56 @@ repositories {
mavenCentral()
}

dependencies {
testImplementation(libs.mockito)
testImplementation(libs.bundles.test)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

sourceSets {
create("integrationTest") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}
}

tasks.register<Test>("integrationTest") {
description = "Runs integration tests."
group = "verification"

testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
shouldRunAfter("test")

useJUnitPlatform()
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

tasks.check {
dependsOn(tasks.ktlintCheck)
dependsOn("integrationTest")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
dependsOn(tasks.ktlintFormat)
}

val integrationTestImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}

dependencies {
testImplementation(libs.mockito)
testImplementation(libs.bundles.test)

integrationTestImplementation(libs.bundles.test)
integrationTestImplementation(libs.bundles.test.containers)
integrationTestImplementation(libs.bundles.test.dbs)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
168 changes: 168 additions & 0 deletions lib/src/integrationTest/kotlin/net/samyn/kapper/QueryTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package net.samyn.kapper

import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Named.named
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource
import org.testcontainers.containers.JdbcDatabaseContainer
import org.testcontainers.containers.MySQLContainer
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.sql.DriverManager
import java.util.UUID

@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class QueryTests {
companion object {
@Container
val postgresql = PostgreSQLContainer("postgres:16")

@Container
val mysql = MySQLContainer("mysql:8.4")

val allContainers =
mapOf(
"PostgreSQL" to postgresql,
"MySQL" to mysql,
)

@JvmStatic
fun databaseContainers() = allContainers.map { arguments(named(it.key, it.value)) }
}

val superman = SuperHero(UUID.randomUUID(), "Superman", "[email protected]", 86)
val batman = SuperHero(UUID.randomUUID(), "Batman", "[email protected]", 85)
val spiderMan = SuperHero(UUID.randomUUID(), "Spider-man", "[email protected]", 62)

@BeforeAll
fun setup() {
allContainers.values.forEach { container ->
setupDatabase(container)
}
}

private fun setupDatabase(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
connection.createStatement().use { statement ->
statement.execute(
"""
CREATE TABLE super_heroes (
id ${if (container is MySQLContainer) "VARCHAR(36)" else "UUID"} PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
age INT
);
""".trimIndent().also {
println(it)
},
)
statement.execute(
"""
INSERT INTO super_heroes (id, name, email, age) VALUES
('${superman.id}', '${superman.name}', '${superman.email}', ${superman.age}),
('${batman.id}', '${batman.name}', '${batman.email}', ${batman.age}),
('${spiderMan.id}', '${spiderMan.name}', '${spiderMan.email}', ${spiderMan.age});
""".trimIndent().also {
println(it)
},
)
}
}
}

private fun createConnection(container: JdbcDatabaseContainer<*>) =
DriverManager.getConnection(container.jdbcUrl, container.username, container.password)

@ParameterizedTest()
@MethodSource("databaseContainers")
@Order(1)
fun `database should be running`(container: JdbcDatabaseContainer<*>) {
container.isRunning.shouldBeTrue()
}

@ParameterizedTest()
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query all users`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes = connection.query<SuperHero>("SELECT * FROM super_heroes")
heroes.shouldContainExactlyInAnyOrder(
superman,
batman,
spiderMan,
)
}
}

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query users with condition`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes = connection.query<SuperHero>("SELECT * FROM super_heroes WHERE age > :age", "age" to 80)
heroes.shouldContainExactlyInAnyOrder(
superman,
batman,
)
}
}

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query specific columns`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
connection.query<SuperHero>(
"SELECT id, name FROM super_heroes WHERE name = :name",
"name" to superman,
)
heroes.shouldContainExactlyInAnyOrder(
SuperHero(superman.id, superman.name),
)
}
}

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should handle empty result set`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
connection.query<SuperHero>(
"SELECT * FROM super_heroes WHERE name = :name",
"name" to "joker",
)
heroes.shouldBeEmpty()
}
}

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query with multiple conditions`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
connection.query<SuperHero>(
"SELECT * FROM super_heroes WHERE age BETWEEN :fromAge AND :toAge",
"fromAge" to 80,
"toAge" to 89,
)
heroes.shouldContainExactlyInAnyOrder(
superman,
batman,
)
}
}

data class SuperHero(val id: UUID, val name: String, val email: String? = null, val age: Int? = null)
}

0 comments on commit 37f25d2

Please sign in to comment.