Created
August 24, 2024 11:24
-
-
Save jakzal/bc6f58aea0573500a9e0849e168a30ee to your computer and use it in GitHub Desktop.
Using Testcontainers with Kotlin Exposed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment.exposed | |
import org.jetbrains.exposed.sql.* | |
import org.jetbrains.exposed.sql.transactions.transaction | |
import org.junit.jupiter.api.BeforeEach | |
import org.testcontainers.containers.PostgreSQLContainer | |
import org.testcontainers.junit.jupiter.Container | |
import org.testcontainers.junit.jupiter.Testcontainers | |
import org.testcontainers.utility.DockerImageName | |
import payment.Payment | |
import payment.PaymentRepositoryContract | |
@Testcontainers | |
class ExposedPaymentRepositoryTest : | |
PaymentRepositoryContract(ExposedPaymentRepository(postgresql.connection)) { | |
companion object { | |
@Container | |
private val postgresql = PostgreSQLContainer(DockerImageName.parse("postgres:16-alpine")) | |
} | |
@BeforeEach | |
fun createSchema(): Unit = transaction(postgresql.connection) { | |
addLogger(StdOutSqlLogger) | |
SchemaUtils.create(Payments) | |
} | |
override fun givenPayments(vararg payments: Payment): Unit = transaction(postgresql.connection) { | |
addLogger(StdOutSqlLogger) | |
payments.forEach { payment -> | |
Payments.insert { | |
it[userId] = payment.userId.value | |
it[amount] = payment.amount.value | |
it[category] = payment.category.name | |
it[completedAt] = payment.completedAt | |
it[description] = payment.description | |
} | |
} | |
} | |
} | |
private val <SELF : PostgreSQLContainer<SELF>> PostgreSQLContainer<SELF>.connection | |
get() = Database.connect(url = jdbcUrl, user = username, password = password) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment.inmemory | |
import payment.Payment | |
import payment.PaymentRepositoryContract | |
class InMemoryPaymentRepositoryTest : PaymentRepositoryContract(InMemoryPaymentRepository(persistedPayments)) { | |
companion object { | |
private val persistedPayments: MutableList<Payment> = mutableListOf() | |
} | |
override fun givenPayments(vararg payments: Payment) { | |
persistedPayments.addAll(listOf(*payments)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment | |
import kotlinx.datetime.LocalDateTime | |
import java.time.Month.APRIL | |
import java.time.Month.JULY | |
import java.time.YearMonth | |
import kotlin.test.Test | |
abstract class PaymentRepositoryContract(private val repository: PaymentRepository) { | |
@Test | |
fun `it returns an empty list if no payments were found for the user`() { | |
givenPayments( | |
payment("USR-1", "2023-03-31T23:59"), | |
payment("USR-1", "2023-04-01T00:00"), | |
payment("USR-1", "2023-04-30T23:59"), | |
payment("USR-1", "2023-05-01T11:10"), | |
) | |
val payments = repository.findTwoMonthsOfPayments( | |
UserAndMonthCriteria(UserId("USR-2"), YearMonth.of(2023, APRIL)) | |
) | |
assert(payments == emptyList<Payment>()) | |
} | |
@Test | |
fun `it returns an empty list if no payments were found for the month`() { | |
givenPayments( | |
payment("USR-3", "2023-03-31T23:59"), | |
payment("USR-3", "2023-04-01T00:00"), | |
payment("USR-3", "2023-04-30T23:59"), | |
payment("USR-3", "2023-05-01T11:10"), | |
) | |
val payments = repository.findTwoMonthsOfPayments( | |
UserAndMonthCriteria(UserId("USR-3"), YearMonth.of(2023, JULY)) | |
) | |
assert(payments == emptyList<Payment>()) | |
} | |
@Test | |
fun `it returns user payments found for the requested month and one month before`() { | |
givenPayments( | |
payment("USR-4", "2023-02-28T23:59"), | |
payment("USR-4", "2023-03-01T00:00"), | |
payment("USR-4", "2023-03-31T23:59"), | |
payment("USR-4", "2023-04-01T00:00"), | |
payment("USR-4", "2023-04-30T23:59"), | |
payment("USR-4", "2023-05-01T11:10"), | |
) | |
val payments = repository.findTwoMonthsOfPayments( | |
UserAndMonthCriteria(UserId("USR-4"), YearMonth.of(2023, APRIL)) | |
) | |
assert( | |
payments == listOf( | |
payment("USR-4", "2023-03-01T00:00"), | |
payment("USR-4", "2023-03-31T23:59"), | |
payment("USR-4", "2023-04-01T00:00"), | |
payment("USR-4", "2023-04-30T23:59") | |
) | |
) | |
} | |
private fun payment(userId: String, completedAt: String) = Payment( | |
UserId(userId), | |
Amount(2599), | |
LocalDateTime.parse(completedAt), | |
Category("Food"), | |
"Payment for Food" | |
) | |
protected abstract fun givenPayments(vararg payments: Payment) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment | |
import kotlinx.datetime.LocalDateTime | |
data class Payment( | |
val userId: UserId, | |
val amount: Amount, | |
val completedAt: LocalDateTime, | |
val category: Category, | |
val description: String | |
) | |
@JvmInline | |
value class Amount(val value: Int) | |
@JvmInline | |
value class Category(val name: String) | |
@JvmInline | |
value class UserId(val value: String) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment | |
import java.time.YearMonth | |
interface PaymentRepository { | |
fun findTwoMonthsOfPayments(criteria: UserAndMonthCriteria): List<Payment> | |
} | |
data class UserAndMonthCriteria(val userId: UserId, val month: YearMonth) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment.exposed | |
import org.jetbrains.exposed.sql.Table | |
import org.jetbrains.exposed.sql.kotlin.datetime.datetime | |
object Payments : Table() { | |
private val id = integer("id").autoIncrement() | |
override val primaryKey = PrimaryKey(id) | |
val userId = text("user_id").index() | |
val amount = integer("amount") | |
val completedAt = datetime("completed_at").index() | |
val category = text("category").index() | |
val description = text("description") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment.exposed | |
import kotlinx.datetime.toKotlinLocalDateTime | |
import org.jetbrains.exposed.sql.* | |
import org.jetbrains.exposed.sql.transactions.transaction | |
import payment.* | |
class ExposedPaymentRepository(private val connection: Database) : PaymentRepository { | |
override fun findTwoMonthsOfPayments(criteria: UserAndMonthCriteria): List<Payment> = transaction(connection) { | |
Payments.selectAll() | |
.where { Payments.userId eq criteria.userId.value } | |
.andWhere { | |
Payments.completedAt greaterEq criteria.month.minusMonths(1).atDay(1).atStartOfDay() | |
.toKotlinLocalDateTime() | |
} | |
.andWhere { | |
Payments.completedAt lessEq criteria.month.atEndOfMonth().atTime(23, 59).toKotlinLocalDateTime() | |
} | |
.orderBy(Payments.completedAt, SortOrder.ASC) | |
.map { it.toPayment() } | |
} | |
private fun ResultRow.toPayment() = Payment( | |
UserId(this[Payments.userId]), | |
Amount(this[Payments.amount]), | |
this[Payments.completedAt], | |
Category(this[Payments.category]), | |
this[Payments.description] | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package payment.inmemory | |
import kotlinx.datetime.toKotlinLocalDateTime | |
import payment.Payment | |
import payment.PaymentRepository | |
import payment.UserAndMonthCriteria | |
class InMemoryPaymentRepository(private val payments: List<Payment>) : PaymentRepository { | |
override fun findTwoMonthsOfPayments(criteria: UserAndMonthCriteria): List<Payment> = payments | |
.filter { it.userId == criteria.userId } | |
.filter { | |
it.completedAt >= criteria.month.minusMonths(1).atDay(1).atStartOfDay() | |
.toKotlinLocalDateTime() | |
} | |
.filter { it.completedAt <= criteria.month.atEndOfMonth().atTime(23, 59).toKotlinLocalDateTime() } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment