测试 KtorClient 时减少代码重复

问题描述

我正在 Ktor client 之上创建服务。我的有效负载是 XML,因此我的客户端的简化版本如下所示:

class MavenClient(private val client : HttpClient) {

    private suspend fun getRemotePom(url : String) =
        try{ MavenClientSuccess(client.get<POMProject>(url)) }catch (e: Exception) { MavenClientFailure(e) 
    }

    companion object {
        fun getDefaultClient(): HttpClient {
            return HttpClient(Apache) {
                install(JsonFeature) {
                    serializer = JacksonSerializer(jackson = kotlinXmlMapper)
                    accept(ContentType.Text.Xml)
                    accept(ContentType.Application.Xml)
                    accept(ContentType.Text.Plain)
                }
            }
        }
    }
}

注意自定义 XMLMapper 的使用,附加到自定义数据类。

我想测试这个类,并按照documentation进行操作。

我的测试客户端最终得到以下代码

private val mockClient = HttpClient(MockEngine) {
    engine {
        addHandler { request ->
            when (request.url.fullUrl) {
                "https://lengrand.me/minimal/1.2/minimal-1.2.pom" -> {
                    respond(minimalResourceStreamPom.readBytes(),headers = headersOf("Content-Type" to listof(ContentType.Application.Xml.toString())))
                }
                "https://lengrand.me/unkNown/1.2/unkNown-1.2.pom" -> {
                    respond("",HttpStatusCode.NotFound)
                }
                else -> error("Unhandled ${request.url.fullUrl}")
            }
        }
    }
    // Todo : How do I avoid repeating this again ? That's my implementation?!
    install(JsonFeature) {
        serializer = JacksonSerializer(jackson = PomParser.kotlinXmlMapper)
        accept(ContentType.Text.Xml)
        accept(ContentType.Application.Xml)
        accept(ContentType.Text.Plain)
    }
}
private val Url.hostWithPortIfrequired: String get() = if (port == protocol.defaultPort) host else hostWithPort
private val Url.fullUrl: String get() = "${protocol.name}://$hostWithPortIfrequired$fullPath"

private val mavenClient = MavenClient(mockClient)

现在,我不担心 Mapper 本身,因为我直接测试它。 然而,困扰我的是我基本上必须复制我的客户的完整逻辑来测试行为? 这看起来很脆弱,因为例如它会导致我的测试失败并且如果我明天转移到 Json 就必须更新。例如,如果我开始使用 Response Validation,也一样。 对于我使用 defaultRequest 的另一个客户端来说更是如此,我也必须完全复制它:

private val mockClient = HttpClient(MockEngine) {
    install(JsonFeature) {
        serializer = JacksonSerializer(mapper)
        accept(ContentType.Application.Json)
    }
    defaultRequest {
        method = HttpMethod.Get
        host = "api.github.com"
        header("Accept","application/vnd.github.v3+json")
        if (GithubLogin().hasToken()) header("Authorization",GithubLogin().authToken)
    }

我做错了吗?我测试太多了吗?我很好奇如何改进这一点。

非常感谢您的意见!

P.S :无关,但关于在 Ktor 上进行测试的页面提到将依赖项添加implementation。听起来我应该改testImplementation 以避免随我的应用程序一起传送库?

解决方法

MockEngine 旨在对真实的 HTTP 客户端实现进行存根以测试使用它的对象。您遇到的重复问题在于转换响应主体责任属于客户端。所以我建议要么直接使用 Jackson 来转换响应体(在这种情况下你不需要使用 JsonFeature)或者在扩展函数中提取公共配置并为两个引擎调用它。>