Helio
06/16/2022, 7:09 AMtestApplication
starts a separate Classloader where the mocked classes/objects are not visible. Is this expected?Helio
06/16/2022, 7:09 AMAleksei Tirman [JB]
06/16/2022, 7:30 AMArtifactoryClient
and how it’s related to Ktor?Helio
06/16/2022, 7:31 AMHelio
06/16/2022, 7:31 AMclass ArtifactoryClient(private val adminToken: String) {
companion object {
private const val EXPIRY_TIME = 30L
private val cache =
CacheBuilder.newBuilder().expireAfterWrite(Duration.ofMinutes(EXPIRY_TIME))
.build<String, ArtifactoryClient>()
private const val cacheKey = "expiryObjectKey"
private val mutex = Mutex()
suspend fun getClient(): ArtifactoryClient {
val client = cache.getIfPresent(cacheKey)
if (client == null) {
mutex.withLock {
return cache.get(cacheKey) { runBlocking { generateClient() } }
}
} else {
return client
}
}
private suspend fun generateClient(): ArtifactoryClient {
val tokenatorToken = TokenatorClient.getAdminAccessToken()
return ArtifactoryClient(tokenatorToken.token)
}
}
private val artifactoryUrlString = "<https://artifactoryUrl>"
private val artifactoryUrl = URLBuilder(artifactoryUrlString).build()
private val client = HttpClient(CIO) {
expectSuccess = true
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
defaultRequest {
url {
protocol = artifactoryUrl.protocol
port = artifactoryUrl.port
host = artifactoryUrl.host
}
headers {
append("Authorization", "Bearer $adminToken")
}
}
}
suspend fun getItemPermissions(repoKey: String, itemPath: String): ArtifactoryItemPermissions {
return client.get {
url { appendPathSegments("api", "storage", repoKey, itemPath) }
parameter("permissions", "")
}.body()
}
}
Helio
06/16/2022, 7:36 AMKtor HttpClient
we use to forward the requests. In our Class, we do have a companion object that has the getClient()
that will return the HttpClient
if it already exists and it is cached, or it will use another Ktor HttpClient (TokenatorClient
) To send a request to another service to get a token and assign it to the HttpClient of Artifactory client.Aleksei Tirman [JB]
06/16/2022, 7:36 AMgetClient()
method, that you mock, returns an instance of ArtifactoryClient
and not the HttpClient
.Aleksei Tirman [JB]
06/16/2022, 7:38 AMHelio
06/16/2022, 7:39 AMcoEvery { ArtifactoryClient.getClient() } returns artifactoryClient
What we are actually trying to understand is if there was any change in Ktor V2 that perhaps could’ve affected this from being mocked? I’m not sure if I missed anything in the documentation.Helio
06/16/2022, 7:40 AMprivate val artifactoryClient = mockk<ArtifactoryClient>()
🤔Aleksei Tirman [JB]
06/16/2022, 7:41 AMArtifactoryClient
class mocking is failed?Helio
06/16/2022, 7:43 AMKtor 2.0.2
, the method that was previously being ArtifactoryClient.getClient()
is no longer identified as “mocked” when running the integration tests.
Again, I’m not saying that it is an issue with Ktor, I’m honestly just trying to understand if there was any change around the versions that could’ve triggered this behaviour change.Helio
06/16/2022, 7:46 AM43
the itemPermissions would be the return from itemPermissions
in setUpMockArtifactory()
. But instead, the tests now are trying to access the real value of ArtifactoryClient.getClient().getItemPermissons(…m…)
Helio
06/16/2022, 7:47 AMtestApplication
starts a separate ClassLoader where the mocked classes/objects are not visible.Aleksei Tirman [JB]
06/16/2022, 7:49 AMDuring our investigation we suspected that theThat’s true because thestarts a separate ClassLoader where the mocked classes/objects are not visible.testApplication
development
mode is on in the test environment that enables auto reloading of classes. Probably your problem is related to https://youtrack.jetbrains.com/issue/KTOR-4164.Aleksei Tirman [JB]
06/16/2022, 7:51 AMtestApplication
?Aleksei Tirman [JB]
06/16/2022, 7:52 AMdevelopment
mode cannot be switched off there.Helio
06/16/2022, 7:54 AMtestApplication
?Helio
06/16/2022, 7:55 AMAleksei Tirman [JB]
06/16/2022, 8:09 AM@Test
fun test() {
val engine = TestApplicationEngine(createTestEnvironment {
developmentMode = false
module {
routing {
get("/") {
call.respondText { "OK" }
}
}
}
})
assertFalse(engine.environment.developmentMode)
engine.start(wait = false)
val body = runBlocking {
engine.client.get("/").bodyAsText()
}
assertEquals("OK", body)
engine.stop()
}
Helio
06/16/2022, 8:14 AM@Test
fun sendEventsToStreamHub() {
val respBody = "{\"eventId\":\"94a16a71-4525-40f9-84e1-7178b05a8851\",\"ingestionTime\":\"2021-06-07T01:22:44.920Z\"}"
val streamHubClient = givenStreamHubResponse(HttpStatusCode.OK, respBody)
mockkObject(StreamHubClient)
every { StreamHubClient.getHttpClient() } returns streamHubClient
setUpMockArtifactory()
val engine = TestApplicationEngine(createTestEnvironment {
developmentMode = false
module {
routing {
post("/permissions/ccm") {
runCatching {
ArtifactoryPermissionService.sendCcmPermissionsToStreamHub()
}.onSuccess {
call.respond(HttpStatusCode.OK)
}
}
}
}
})
val response = runBlocking {
<http://engine.client.post|engine.client.post>("/permissions/ccm")
}
assertEquals(HttpStatusCode.OK, response.status)
}
Helio
06/16/2022, 8:17 AMHelio
06/16/2022, 8:18 AMHelio
06/16/2022, 8:19 AMHelio
06/16/2022, 8:19 AMAleksei Tirman [JB]
06/16/2022, 8:33 AMIIUC, this is what you would expect me my test to be, right?I don’t know. It depends on what behavior do you want to test.
Do you mind to kindly explain what was the rational behind what you did, please? I would love to understand what we’ve done here.I just use the
TestApplicationEngine
class directly (the testApplication
function uses it under the hood).