I’m having trouble converting a test that uses laz...
# kotest
s
I’m having trouble converting a test that uses lazy and beforeEach into a kotest, as the lateinit variable is not set by the time that I
include
the testfactory. am I going to need to use some funky setup here or am I just missing something?
trying to convert from this:
Copy code
class DiskRecordingOneSourceApiTest : OneSourceApiTests {

    override val uri by lazy { Uri.of("<http://localhost>:${servirtium.port()}") }

    override val secret: String
        get() = System.getenv("ONESOURCE_CLIENT_SECRET")
    override val id: String
        get() = System.getenv("ONESOURCE_CLIENT_ID")

    private lateinit var servirtium: ServirtiumServer

    @BeforeEach
    fun start(info: TestInfo) {
        servirtium = ServirtiumServer.Recording(
            info.markdownName(),
            ONE_SOURCE_LOGIN_URL,
            Disk(File("src/test/resources/servitium-recordings")),
            OneSourceInteractionOptions
        )
        servirtium.start()
    }

    @AfterEach
    fun stop() {
        servirtium.stop()
    }
}
to this:
Copy code
class DiskRecordingOneSourceApiKotestTest : FreeSpec({
    lateinit var servirtium: ServirtiumServer
    
    beforeEach {
        servirtium = ServirtiumServer.Recording(
            it.name.toString(),
            ONE_SOURCE_LOGIN_URL,
            Disk(File("src/test/resources/servitium-recordings")),
            OneSourceInteractionOptions
        )
        servirtium.start()
    }
    val uri by lazy { Uri.of("<http://localhost>:${servirtium.port()}") }
    
    include(oneSourceApiTests(uri, System.getenv("ONESOURCE_CLIENT_SECRET"), System.getenv("ONESOURCE_CLIENT_ID")))
    
    afterEach {
        servirtium.stop()
    }
})


fun oneSourceApiTests(uri: Uri, secret: String, id: String) = freeSpec {
    "login is a success" - {
        val updateService = UpdateService(uri, HttpConfig().ssmClient())
        val loginResult = updateService.performLogin(id, secret)
        expectThat(loginResult)
            .isA<Success<LoginResponseSuccess>>()
            .get { this.value }
            .get { success }
            .isEqualTo("approved")
    }
}
oh and the equivalent interface for the non-kotest version is
Copy code
@JvmDefaultWithCompatibility
interface OneSourceApiTests {
    val uri: Uri
    val secret: String
    val id: String

    @Test
    fun `login is a success`() {
        val updateService = UpdateService(uri, HttpConfig().ssmClient())
        val loginResult = updateService.performLogin(id, secret)
        expectThat(loginResult)
            .isA<Success<LoginResponseSuccess>>()
            .get { this.value }
            .get { success }
            .isEqualTo("approved")
    }
}
I should also probably be more specific about the error message lol:
Copy code
lateinit property servirtium has not been initialized
kotlin.UninitializedPropertyAccessException: lateinit property servirtium has not been initialized
	at com.blah.login.servirtium.DiskRecordingOneSourceApiKotestTest$1$uri$2.invoke(DiskRecordingOneSourceApiKotestTest.kt:22)
where line 22 is the
val uri by lazy { Uri.of("<http://localhost>:${servirtium.port()}") }
l
I think the problem lives in you calling a function with the
Uri
object. It's not really lazy as you'll force an access to it
(and the port won't be ready yet)
I think the quickest solution to this would be to create your
val uri
inside
beforeEach
s
hm. as a lateinit? I didn’t think of that.
l
Yes, similarly to how you've done with
servirtum
. I think you're trying to access it before you it's ready
If there is a way to
awaitStart
it would be even better
s
still doesn’t like it 😕
i think the problem is the include is running before the beforeEach entirely..
l
Yep, that's very likely
s
which kinda makes sense.
I just can’t find anything in the docs pertaining to this.
l
That's why I was trying to rotate the initializations so to have it done before tests start executing
Maybe put it in beforeAll? This way no tests will even be included before you start the server
But I'm just guessing now 😂
s
I think (according to the servicium docs) that it needs to be started before each test…
else I’d do like a
prepareSpec
or something
l
Do you need it to run inside
beforeEach
? If you're doing a one-time only setup, you can put it directly in the body of the spec
That way you wouldn't ever even jump to a different thread, once your variable is ready tests can start
s
it needs (I think) access to the test name
i can try without the test name to see what happens, but I’m guessing it will break.
yeah needs a name of some sort, won’t compile otherwise.
l
You can maybe move the logic to an Extension or to a Listener?
It seems more complex than just a
before start
situation, as you're dealing with test names, for instance
s
yeah I had looked at them, but I thought that it had said they’re functionally the same as a beforeEach block, so I didn’t even try. but I’m not seeing that now. let me try.
l
They are almost the same. We represent
beforeEach
as an extension, internally. However, an extension has more power
s
oh yeah, it says
Extensions are reusable lifecycle hooks. In fact, lifecycle hooks are themselves represented internally as instances of extensions.
and then on the lifecycle hooks page it says:
The second, related, method is to override the callback functions in the Spec. This is essentially just a variation on the first method.
oh ok… how do I return state from the extension then?
ok, I have no clue why this one wouldn’t work. I’m really not understanding something here. Instead of doing the before each outside of the testfactory, I do it inside the factory and it still fails:
Copy code
fun oneSourceApiTests(
    recording: Boolean = false,
    secret: String,
    id: String
) = freeSpec {
    lateinit var servirtium: ServirtiumServer
    val uri by lazy { Uri.of("<http://localhost>:${servirtium.port()}") }

    beforeEach {
        servirtium = if (recording) {
            ServirtiumServer.Recording(
                it.name.toString(),
                ONE_SOURCE_LOGIN_URL,
                InteractionStorage.Disk(File("src/test/resources/servitium-recordings")),
                OneSourceInteractionOptions
            )
        } else {
            ServirtiumServer.Replay(
                it.name.toString(),
                InteractionStorage.Disk(File("src/test/resources/servitium-recordings")),
                OneSourceInteractionOptions
            )
        }
        servirtium.start()
    }
    
    afterEach {
        servirtium.stop()
    }

    "login is a success" - {
        val updateService = UpdateService(uri, HttpConfig().ssmClient())
        val loginResult = updateService.performLogin(id, secret)
        expectThat(loginResult)
            .isA<Success<LoginResponseSuccess>>()
            .get { this.value }
            .get { success }
            .isEqualTo("approved")
    }
}


class DiskRecordingOneSourceApiKotestTest : FreeSpec({
    include(oneSourceApiTests(true, System.getenv("ONESOURCE_CLIENT_SECRET"), System.getenv("ONESOURCE_CLIENT_ID")))
})
I tried with both lazy and having the uri instantiated in the beforeEach. same failure for both.
ok, got a bit further. i was using leaf nodes instead of container nodes, still getting failures, but they’re different now. I was trying leaf nodes because I had originally been getting errors about root nodes about 20 steps back.
hm. yeah, I don’t think I’m going to be able to make this work. ugh.
if anyone else wants to ever try to get this working, this demo repo from http4k demonstrates how it should work. https://github.com/http4k/servirtium-demo-kotlin-climate-tck/tree/master
m
You could instead of having a lazy val uri have a function returning the uri and try passing that in the thing you pass into the include, and then invoke this function where you need the uri in your test cases. Like this the actual access to your servirtium lateinit var should be delayed to the actual execution of the test. Have you tried that?
s
I thought about that but didn’t try it. i would have to pass the servirtium object through that and it just seemed messy.
m
You could have something like this:
Copy code
val uriFn = { Uri.of(...) }
And then pass this one into your contract. You'd then have no need to pass around the servirtium object, or am I missing something? 🤔
s
the uri depends on the port coming from the servirtium instance, which is only created in the before each block. I think there’s probably a way to make it work, but even moving the entire beforeEach block into the test factory still didn’t work completely so I’m not sure what could be going on there.
m
Yes, you would use that inside the stuff I dotted out, because I'm lazy :D Since it's a function, it will get evaluated on invocation and not instantly on definition of uriFn (at least that's what I expect). The uriFn should then have a signature of
() -> Uri
, and thus could just be invoked. Okay, seems like this is more complicated, reading your last comment :D
s
yeah, if I passed the function through, then the uri function would no longer have scope for the servirtium instance (I think) as it would be in a completely different function that it would be being invoked. Therefore I’d have to pass the servirtium instance as well, but I don’t think that would work, because as I described, it didn’t even work when I moved all the logic to the test factory. no clue what I’m doing wrong, but it should be duplicatable with the above demo repo. I have spent way too long on this already though, so I can’t really continue trying to track the problem down. 😞
l
I'll checkout the project and see what needs to be done. If I can't do it in 20 minutes I'll give up 😛
Per the initial description, you're dealing with Climate Change API? Have you checked open meteo? https://open-meteo.com/
Do you have your full test available @snowe? I want to try to run it
s
no, that’s just an example repo from the http4k climate docs. I’m working on internal business stuff I can’t completely share. that’s why I said it could be duplicated with the demo repo most likely
because I am pretty much doing exactly the same as the example repo