Kontainers with JUnit Jupiter
Kontainers integrates with JUnit using JUnit Jupiter extensions. These extensions enable Kontainers to easily used with JUnit for integration testing.
Kontainers can be injected into tests as a constructor parameters, which is useful in cases where you need access to the running Kontainer.
Kontainers can also be used simply as properties suppliers to integration tests via another set of annotations.
Usage
Both extensions are available as a single dependency:
dependencies {
testImplementation("io.microkt.kontainers:kontainers-junit5"))
}
JUnit Parameter Extension
To use Kontainers are constructor parameters, annotate your test suite
with the @Kontainers
annotation and pass the Kontainer(s) you'd like to use to
the constructor.
The Kontainers JUnit extension will manage your Kontainer's lifecycle for you. It will be started before your tests are run and torn down after tests complete, regardless of test failures.
@Kontainers
internal class MysqlKontainerTest(private val mysql: MysqlKontainer) {
private var dataSource: DataSource = buildDataSource(mysql)
@Test
fun testQuery() {
val statement: Statement = dataSource.connection.createStatement()
statement.execute("SELECT 1")
val resultSet = statement.resultSet
resultSet.next()
val resultInt = resultSet.getInt(1)
assertEquals(1, resultInt, "SELECT 1; should return 1")
}
companion object {
private fun buildDataSource(jdbcKontainer: JdbcKontainer): DataSource =
HikariDataSource(
HikariConfig().apply {
jdbcUrl = jdbcKontainer.createJdbcUrl()
username = jdbcKontainer.getUsername()
password = jdbcKontainer.getPassword()
}
)
}
}
Customizing Parameter Kontainers
Using the JUnit 5 plugin, you can customize the Kontainer using the annotation
@KontainerSpecOverride
to update the KontainerSpec
used to create the
Kontainer
.
@KontainerSpecOverride
can be used to override values on each Kontainer
constructor parameter. For example, if your integration test needs Redis and
Localstack, you may want to apply customizations to each Kontainer to meet
your requirements. You may need to override the Redis image, while also
customizing which AWS services are started by LocalStack.
internal class RedisBusterSpecProvider : KontainerSpecProvider {
override fun override(kontainerSpec: KontainerSpec): KontainerSpec =
kontainerSpec(kontainerSpec) {
image = "redis:6.2-buster"
}
}
internal class LocalstackSpecProvider : KontainerSpecProvider {
override fun override(kontainerSpec: KontainerSpec): KontainerSpec =
kontainerSpec(kontainerSpec) {
environment {
set("SERVICS" to "sqs")
}
}
@Kontainers
internal class MyIntegrationTest(
@KontainerSpecOverride(LocalstackSpecProvider::class)
private val localstack: LocalstackKontainer
@KontainerSpecOverride(RedisBusterSpecProvider::class)
private val redis: RedisKontainer
) {
// tests here
}
JUnit Property Supplier Extensions
Property supplier Kontainers are useful when you want to expose properties to an integration test, but you don't need to reference the underlying Kontainer instance in your tests.
A good example of such use cases are the Spring Boot and Micronaut integrations.
Generic Kontainer
For non-relational database containers, the most appropriate property supplier
annotation is @Kontainer
. @Kontainer
requires a single value, the class of
the Kontainer to run.
For example, to run Redis:
@Kontainer(RedisKontainer::class)
internal class IntegrationTest {
// tests
}
Using Property Suppliers
By default, property suppliers are provided for common framework properties
that are applicable to the Kontainers run with a @Kontainer
annotation.
However, you may supply your own properties by writing a property supplier.
For example, if you'd like to export a property named redis.uri
containing
the URI to the running Redis Kontainer, you can write a property supplier.
class MyRedisPropertySupplier : PropertySupplier {
override fun supply(kontainer: Kontainer): Map<String, String> =
when (kontainer) {
is RedisKontainer -> redisProps(kontainer)
else -> mapOf()
}
private fun redisProps(kontainer: RedisKontainer): Map<String, String> =
mapOf(
"redis.uri" to "redis://${kontainer.getAddress()}/${kontainer.getPort()}"
)
Then use your property supplier in your integration test:
@Kontainer(
RedisKontainer::class,
propertySuppliers = [MyRedisPropertySupplier::class]
)
internal class IntegrationTest {
@Test
fun testProps() {
assertNotNull(System.getProperty("redis.uri"))
}
}
Database Kontainer
@DatabaseKontainer
is semantically similar to Generic Kontainer, however it
provides JDBC and R2DBC specific properties to integration tests. @DatabaseKontainer
should be preferred when using containerized databases because it has more reliable
support for determining when a database is ready to accept client connections.
Similarly to @Kontainer
, you can use your own custom property suppliers.
Sample using PostgreSQL with Spring Boot:
@SpringBootTest
@DatabaseKontainer(PostgresKontainer::class)
class DemoApplicationTests {
@Autowired
private final lateinit var animalRepository: ReactiveAnimalRepository
@Test
fun contextLoads() = runBlocking {
assertEquals("dog", animalRepository.findByName("dog").awaitFirst().name)
}
}