测试 KtorClient 时减少代码重复

Posted

技术标签:

【中文标题】测试 KtorClient 时减少代码重复【英文标题】:Reducing code duplication when testing a KtorClient 【发布时间】:2021-07-03 01:10:48 【问题描述】:

我正在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 来避免将 lib 与我的应用程序一起发送?

【问题讨论】:

为什么不将公共代码提取到getDefaultClientConfig(): HttpClientConfig&lt;*&gt;.() -&gt; Unit函数中呢?所以你可以用fun getDefaultClient() = HttpClient(Apache) getDefaultClientConfig(); /* and some specific config for production */ private val mockClient = HttpClient(MockEngine) getDefaultClientConfig(); /* and some specific config for tests */调用它 【参考方案1】:

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

【讨论】:

以上是关于测试 KtorClient 时减少代码重复的主要内容,如果未能解决你的问题,请参考以下文章

在定义可交换操作时减少代码重复

通过多个 OAuth2 提供程序登录时如何减少代码重复?

使用 UITableViewController 时如何尽量减少重复自己

如何使用 Angular Decorator 减少重复代码?

减少公共代码调用公共类的代码重复

javascript 使用Prototype属性减少重复代码