Jerry Preissler
05/28/2023, 10:08 AMclass DokumentMemoryPersistenceAdapter: LoadDokumentPort, SaveDokumentPort {
....
}
a Koin module
val persistenceKoinModule = module(createdAtStart = true) {
singleOf(::DokumentMemoryPersistenceAdapter) {
bind<LoadDokumentPort>()
bind<SaveDokumentPort>()
}
}
and a test
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DokumentPersistenceAdapterTest : KoinTest {
private val saveDokumentPort by inject<SaveDokumentPort>()
private val loadDokumentPort by inject<LoadDokumentPort>()
@Test
fun createAndLoadOkTest() {
println("*****************************************")
println(saveDokumentPort)
println(loadDokumentPort)
/*.....
/*save a new Dokument via saveDokumentPort, reload via loadDokumentPort
/*run assertions
}
}
If I run only the test createAndLoadOkTest, everything works fine and I can see in stdout that both ports are provided by the same instance:
org.codeshards.aktenordner.adapter.outgoing.persistence.DokumentMemoryPersistenceAdapter@25b2cfcb
org.codeshards.aktenordner.adapter.outgoing.persistence.DokumentMemoryPersistenceAdapter@25b2cfcb
But if I run alll tests within the class, the tests fails and I can see that the ports are provided by two instances instead:
org.codeshards.aktenordner.adapter.outgoing.persistence.DokumentMemoryPersistenceAdapter@60dce7ea
org.codeshards.aktenordner.adapter.outgoing.persistence.DokumentMemoryPersistenceAdapter@662f5666
I'm not really familiar with Koin yet, but my understanding is that the declaration _singleOf_(::DokumentMemoryPersistenceAdapter)
ensures that the component is a singleton. Is this wrong?Jerry Preissler
05/28/2023, 10:31 AMMarcello Galhardo
05/28/2023, 3:46 PMsingleOf
should create a singleton, your understanding is correct. I would guess the problem is in the KoinTest
itself, but I'm not familiar with its internal implementation.
I would recommend 3 things (running all tests), to better map where the problem is:
1. Add logs to both setup (before) and tear down (after) to try to identify when the instances changed.
2. Force to get both instances in set-up to see if the tests will fail (edit: the previous log will actually do that, so ignore this step).
3. Try using startKoin and stopKoin on setup and tear down to see if the problem persists (https://insert-koin.io/docs/reference/koin-test/testing/#starting--stopping-koin-for-your-tests).Marcello Galhardo
05/28/2023, 3:53 PMKoinTest
will reset the component per test.
2. inject
will hold a reference (like kotlin.Lazy
).
3. If your first test doesn't access both references, you will get a different reference in the second test execution but reuse the one that has been loaded.
That seems to be a design problem, Koin's inject
should not hold a reference between tests if the default behaviour is to reinitializing the component. You probably should fill a bug for that.
In the meantime, I think you can fix the issue by changing your code as follows:
1. Change SaveDocumentPort
and LoadDocumentPort
to lateinit var
.
2. Reload it on set-up (Before) using get
instead of inject
.
That should ensure you always have a new instance per test, and respect the singleton constraint.
cc: @arnaud.giulianiJerry Preissler
05/28/2023, 3:56 PMJerry Preissler
05/28/2023, 8:50 PMMartin Sloup
05/29/2023, 7:24 AMprivate val saveDokumentPort: SaveDokumentPort
get() = get<SaveDokumentPort>()
private val loadDokumentPort : LoadDokumentPort
get() = get<LoadDokumentPort>()
Martin Sloup
05/29/2023, 7:25 AMinject<T>()
is actually lazy { get<T>() }
arnaud.giuliani
05/29/2023, 10:04 AMKoinTest
offers a interface marker to help inject with ìnject()
function. In you case you can add logs to Koin, to see how your test is restarting Koin:
startKoin{
printLogger(DEBUG)
}
Jerry Preissler
05/29/2023, 1:35 PMarnaud.giuliani
05/30/2023, 7:19 AM