Skip to main content
Version: 6.3 🚧

Koin

Koin​

The Koin DI Framework can be used with Kotest through the KoinExtension extension.

To use the extension in your project, add the dependency to your project:

note

Since Kotest 6.0, all extensions are published under the io.kotest group once again, with version cadence tied to main Kotest releases.

io.kotest:kotest-extensions-koin:${kotestVersion}

With the dependency added, we can easily use Koin in our tests!

class KotestAndKoin : KoinTest, FunSpec() {
init {
extension(KoinExtension(koinModule) { mockk<UserService>() })

test("use userService") {
val userService by inject<UserService>()

userService.getUser().username shouldBe "LeoColman"
}
}
}

By default, the extension will start/stop the Koin context between leaf tests. If you are using a nested spec style (like DescribeSpec) and instead want the Koin context to persist over all leafs of a root tests (for example to share mocked declarations between tests), you can specify the lifecycle mode as KoinLifecycleMode.Root in the KoinExtension constructor.

class KotestAndKoin : KoinTest, DescribeSpec() {

init {
extension(KoinExtension(module = myKoinModule, mode = KoinLifecycleMode.Root))

describe("use userService") {
val userService by inject<UserService>()

it("inside a leaf test") {
userService.getUser().username shouldBe "LeoColman"
}

it("this shares the same context") {
userService.getUser().username shouldBe "LeoColman"
}
}
}
}

Building fresh modules per test​

KoinExtension reuses the Module instances you pass to it for the whole spec. If you mutate the Koin context inside a test — for example with KoinTest.declare — reusing the same module instance can leak singleton state into later tests, because Koin caches instances on the factories held by the module (see #6006 and the upstream koin#2412).

To avoid this, pass a module factory lambda instead of pre-built instances. The factory is invoked every time Koin is started, so each test gets a brand new module (and therefore fresh singletons):

class KotestAndKoin : KoinTest, FunSpec() {
init {
// myModule() builds a new Module instance on each invocation
extension(KoinExtension { listOf(myModule()) })

test("each test gets a clean koin context") {
val userService by inject<UserService>()

userService.getUser().username shouldBe "LeoColman"
}
}
}