unit-testing - 如何使用 SPOCK 框架为 HTTPBuilder 编写单元测试?
问题描述
我希望单元测试通过成功和失败执行路径。如何使测试用例走向成功或失败路径?
void addRespondents()
{
http.request(POST, TEXT) {
uri.path = PATH
headers.Cookie = novaAuthentication
headers.Accept = 'application/json'
headers.ContentType = 'application/json'
body = respondentString
response.success = { resp, json ->
statusCode = 2
}
response.failure = { resp, json ->
if(resp.status == 400) {
statusCode = 3
def parsedJson = new JsonSlurper().parse(json)
}else{
autoCreditResponse = createErrorResponse(resp)
}
}
}
}
解决方案
好的,看来你使用这个库:
<dependency>
<groupId>org.codehaus.groovy.modules.http-builder</groupId>
<artifactId>http-builder</artifactId>
<version>0.7.1</version>
</dependency>
因为我以前从未使用过 HTTPBuilder,而且在使用 Groovy 时它看起来是一个不错的工具,所以我尝试了一下,复制了您的用例,但将其转换为完整的MCVE。我不得不承认这个库的可测试性很糟糕。甚至库本身的测试也不是适当的单元测试,而是集成测试,实际执行网络请求而不是模拟它们。该工具本身还包含测试模拟或有关如何测试的提示。
因为该功能在很大程度上依赖于闭包中的动态绑定变量,所以模拟测试有点难看,我不得不查看该工具的源代码才能完成它。好的黑盒测试基本上是不可能的,但是您可以通过以下方法注入一个模拟 HTTP 客户端,该客户端返回一个预定义的模拟响应,其中包含足够的信息,不会使应用程序代码脱轨:
待测类
如您所见,我在类中添加了足够的数据,以便能够运行它并做一些有意义的事情。您的方法返回void
而不是可测试的结果并且我们只需要依赖测试副作用的事实并不能使测试更容易。
package de.scrum_master.stackoverflow.q68093910
import groovy.json.JsonSlurper
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.HttpResponseDecorator
import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.Method.POST
class JsonApiClient {
HTTPBuilder http = new HTTPBuilder("https://jsonplaceholder.typicode.com")
String PATH = "/users"
String novaAuthentication = ''
String respondentString = ''
String autoCreditResponse = ''
int statusCode
JsonSlurper jsonSlurper = new JsonSlurper()
void addRespondents() {
http.request(POST, TEXT) {
uri.path = PATH
headers.Cookie = novaAuthentication
headers.Accept = 'application/json'
headers.ContentType = 'application/json'
body = respondentString
response.success = { resp, json ->
println "Success -> ${jsonSlurper.parse(json)}"
statusCode = 2
}
response.failure = { resp, json ->
if (resp.status == 400) {
println "Error 400 -> ${jsonSlurper.parse(json)}"
statusCode = 3
}
else {
println "Other error -> ${jsonSlurper.parse(json)}"
autoCreditResponse = createErrorResponse(resp)
}
}
}
}
String createErrorResponse(HttpResponseDecorator responseDecorator) {
"ERROR"
}
}
斯波克规格
本规范涵盖了上述代码中响应的所有 3 种情况,使用返回不同状态代码的展开测试。
因为被测方法返回,我决定验证实际调用void
的副作用。HTTPBuilder.request
为了做到这一点,我不得不Spy
在HTTPBuilder
. 测试这种副作用是可选的,那么你不需要间谍。
package de.scrum_master.stackoverflow.q68093910
import groovyx.net.http.HTTPBuilder
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.ResponseHandler
import org.apache.http.entity.StringEntity
import org.apache.http.message.BasicHttpResponse
import org.apache.http.message.BasicStatusLine
import spock.lang.Specification
import spock.lang.Unroll
import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.Method.POST
import static org.apache.http.HttpVersion.HTTP_1_1
class JsonApiClientTest extends Specification {
@Unroll
def "verify status code #statusCode"() {
given: "a JSON response"
HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(HTTP_1_1, statusCode, "my reason")
)
def json = "{ \"name\" : \"JSON-$statusCode\" }"
response.setEntity(new StringEntity(json))
and: "a mock HTTP client returning the JSON response"
HttpClient httpClient = Mock() {
execute(_, _ as ResponseHandler, _) >> { List args ->
(args[1] as ResponseHandler).handleResponse(response)
}
}
and: "an HTTP builder spy using the mock HTTP client"
HTTPBuilder httpBuilder = Spy(constructorArgs: ["https://foo.bar"])
httpBuilder.setClient(httpClient)
and: "a JSON API client using the HTTP builder spy"
def builderUser = new JsonApiClient(http: httpBuilder)
when: "calling 'addRespondents'"
builderUser.addRespondents()
then: "'HTTPBuilder.request' was called as expected"
1 * httpBuilder.request(POST, TEXT, _)
where:
statusCode << [200, 400, 404]
}
}
如果你使用 Spock 有一段时间了,可能我不需要解释太多。如果您是 Spock 或模拟测试初学者,可能这有点太复杂了。但是FWIW,我希望如果您研究代码,您可以了解我是如何做到的。我尝试使用 Spock 标签注释来解释它。
控制台日志
控制台日志表明规范涵盖了所有 3 个执行路径:
Success -> [name:JSON-200]
Error 400 -> [name:JSON-400]
Other error -> [name:JSON-404]
如果您使用代码覆盖工具,当然您不需要我在应用程序代码中插入的日志语句。它们仅用于演示目的。
验证结果http.request(POST, TEXT) {...}
为了避免您的方法返回的事实,void
您可以HTTPBuilder.request(..)
通过在 spy 交互中存根方法调用来保存结果,首先传递原始结果,但还要检查预期结果。
只需在块中def actualResult
的某个位置添加(为时已晚),然后将结果分配给它,然后像这样比较:given ... and
when
callRealMethod()
expectedResult
and: "a JSON API client using the HTTP builder spy"
def builderUser = new JsonApiClient(http: httpBuilder)
def actualResult
when: "calling 'addRespondents'"
builderUser.addRespondents()
then: "'HTTPBuilder.request' was called as expected"
1 * httpBuilder.request(POST, TEXT, _) >> {
actualResult = callRealMethod()
}
actualResult == expectedResult
where:
statusCode << [200, 400, 404]
expectedResult << [2, 3, "ERROR"]
如果您更喜欢数据表而不是数据管道,则该where
块如下所示:
where:
statusCode | expectedResult
200 | 2
400 | 3
404 | "ERROR"
我认为这几乎涵盖了在这里测试的所有意义。
推荐阅读
- angular - 如何重新渲染 ngFor
- rest - API 自动化:断言不适用于 google place api
- entity-framework-core - 如何使用不包含外键的现有数据库创建实体之间的关系
- javascript - 文件读取Javascript后全局变量保持不变
- php - 未捕获的错误:调用成员函数错误消息
- hive - 使用 sqoop 从 RDBMS 导入数据到 hive
- android - 如何在条件下使android中的片段活动变灰
- java - 打印具有列和行的二维数组的元素
- api - Telegram API 和 Wrappers:您能否仅通过用户名检查用户是否存在?
- css - 如何覆盖scss文件中的宽度和高度值