Hi there, I'm running into a funny problem with a ...
# koin
j
Hi there, I'm running into a funny problem with a JUnit5 test, maybe someone can help. I have a class that implements 2 Interfaces:
Copy code
class DokumentMemoryPersistenceAdapter: LoadDokumentPort, SaveDokumentPort {
  ....
}
a Koin module
Copy code
val persistenceKoinModule = module(createdAtStart = true) {
    singleOf(::DokumentMemoryPersistenceAdapter) {
        bind<LoadDokumentPort>()
        bind<SaveDokumentPort>()
    }
}
and a test
Copy code
@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:
Copy code
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:
Copy code
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?
I'm one step further now. If I add the println statements in the example above at the start of /all/ tests in the class, everything works. So my guess is that this has to do with lazy initialization of the injected components. I'm still unsure if that's a problem with my understanding of the Singleton in the module declaration or a bug in Koin. I admit that the former is more likely.
m
singleOf
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).
Thinking twice now, and checking (superficially) the code I think I know where the problem is: 1.
KoinTest
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.giuliani
j
Marcello, thanks a lot for the feedback. I'll try out your suggestions and will file a bug so people can follow up.
The workaround with the lateinit var did not work for me, so I switch back to my admittedly crude solution. Details in the issue I opened: https://github.com/InsertKoinIO/koin/issues/1597
m
Maybe use getter instead. Inject keeps instance for class lifecycle:
Copy code
private val saveDokumentPort: SaveDokumentPort
  get() = get<SaveDokumentPort>()

private val loadDokumentPort : LoadDokumentPort
  get() = get<LoadDokumentPort>()
inject<T>()
is actually
lazy { get<T>() }
a
Yes,
KoinTest
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:
Copy code
startKoin{
    printLogger(DEBUG)
}
j
Sorry, I don't quite get it (<rimshot/>). Do I need to import the get<t>() from somewhere?
a
you need to handle local variable more than properties here, as you property will be valued only once