Injecting in Tests
Making your test a KoinComponent with KoinTest
Warning: This does not apply to Android Instrumented tests. For Instrumented testing with Koin, please see Android Instrumented Testing
By tagging your class KoinTest
, your class become a KoinComponent
and bring you:
by inject()
&get()
- function to retrieve your instances from KoincheckModules
- help you check your configurationdeclareMock
&declare
- to declare a mock or a new definition in the current context
class ComponentA
class ComponentB(val a: ComponentA)
class MyTest : KoinTest {
// Lazy inject property
val componentB : ComponentB by inject()
@Test
fun `should inject my components`() {
startKoin {
modules(
module {
single { ComponentA() }
single { ComponentB(get()) }
})
}
// directly request an instance
val componentA = get<ComponentA>()
assertNotNull(a)
assertEquals(componentA, componentB.a)
}
Don't hesitate to overload Koin modules configuration to help you partly build your app.
JUnit Rules
Create a Koin context for your test
You can easily create and hold a Koin context for each of your test with the following rule:
@get:Rule
val koinTestRule = KoinTestRule.create {
// Your KoinApplication instance here
modules(myModule)
}
Specify your Mock Provider
To let you use the declareMock
API, you need to specify a rule to let Koin know how you build your Mock instance. This let you choose the right mocking framework for your need.
Create mocks using Mockito:
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
// Your way to build a Mock here
Mockito.mock(clazz.java)
}
Create mocks using MockK:
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
// Your way to build a Mock here
mockkClass(clazz)
}
!> koin-test project is not tied anymore to mockito
Mocking out of the box
Instead of making a new module each time you need a mock, you can declare a mock on the fly with declareMock
:
class ComponentA
class ComponentB(val a: ComponentA)
class MyTest : KoinTest {
@get:Rule
val koinTestRule = KoinTestRule.create {
modules(
module {
single { ComponentA() }
single { ComponentB(get()) }
})
}
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}
@Test
fun `should inject my components`() {
}
// Replace current definition by a Mock
val mock = declareMock<ComponentA>()
// retrieve mock, same as variable above
assertNotNull(get<ComponentA>())
// is built with mocked ComponentA
assertNotNull(get<ComponentB>())
}
declareMock can specify if you want a single or factory, and if you want to have it in a module path.
Declaring a component on the fly
When a mock is not enough and don't want to create a module just for this, you can use declare
:
@Test
fun `successful declare an expression mock`() {
startKoin { }
declare {
factory { ComponentA("Test Params") }
}
Assert.assertNotEquals(get<ComponentA>(), get<ComponentA>())
}
Checking your Koin modules
Koin offers a way to test if you Koin modules are good: checkModules
- walk through your definition tree and check if each definition is bound
@Test
fun `check MVP hierarchy`() {
checkModules {
modules(myModule1, myModule2 ...)
}
}
Starting & stopping Koin for your tests
Take attention to stop your koin instance (if you use startKoin
in your tests) between every test. Else be sure to use koinApplication
, for local koin instances or stopKoin()
to stop the current global instance.
Testing with JUnit5
JUnit 5 support provides Extensions that will handle the starting and stopping of Koin context. This means that if you are using the extension you don't need to use the AutoCloseKoinTest
.
Dependency
For testing with JUnit5 you need to use koin-test-junit5
dependency.
Writing tests
You need to Register the KoinTestExtension
and provide your module configuration. After this is done
you can either get or inject your components to the test. Remember to use @JvmField
with the @RegisterExtension
.
class ExtensionTests: KoinTest {
private val componentB by inject<Simple.ComponentB>()
@JvmField
@RegisterExtension
val koinTestExtension = KoinTestExtension.create {
modules(
module {
single { Simple.ComponentA() }
single { Simple.ComponentB(get()) }
})
}
@Test
fun contextIsCreatedForTheTest() {
Assertions.assertNotNull(get<Simple.ComponentA>())
Assertions.assertNotNull(componentB)
}
}
Mocking with JUnit5
This works the same way as in JUnit4 except you need to use @RegisterExtension
.
class MockExtensionTests: KoinTest {
val mock: Simple.UUIDComponent by inject()
@JvmField
@RegisterExtension
val koinTestExtension = KoinTestExtension.create {
modules(
module {
single { Simple.UUIDComponent() }
})
}
@JvmField
@RegisterExtension
val mockProvider = MockProviderExtension.create { clazz ->
Mockito.mock(clazz.java)
}
@Test
fun mockProviderTest() {
val uuidValue = "UUID"
declareMock<Simple.UUIDComponent> {
BDDMockito.given(getUUID()).will { uuidValue }
}
Assertions.assertEquals(uuidValue, mock.getUUID())
}
}
Getting the created Koin instances
You can also get the created koin context as a function parameter. This can be achieved by adding a function parameter to the test function.
class ExtensionTests: KoinTest {
@RegisterExtension
@JvmField
val koinTestExtension = KoinTestExtension.create {
modules(
module {
single { Simple.ComponentA() }
})
}
@Test
fun contextIsCreatedForTheTest(koin: Koin) {
// get<SimpleComponentA>() == koin.get<Simple.ComponentA>()
Assertions.assertNotNull(koin.get<Simple.ComponentA>())
}
}