参考链接:http://wiremock.org/docs/getting-started/
在集成测试中模拟外部服务,即当系统需要通过HTTP调用外部服务并获取response,但是我们并不想真的发一个请求时,使用stubs模拟该调用。
只有当需要真实数据时才使用stubs,否则使用mock创建虚拟对象模拟调用。
简单来说,stub就是一段桩代码,当请求匹配时,返回固定的response。
stubFor(get(urlEqualTo("/some/thing"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello world!")));
当请求match url,则返回一个response,状态码为200,Header为 “Content-Type:text/plain",body为"Hello World"
stubFor(post(urlEqualTo("/some/thing2"))
.withRequestBody(matchingXPath("//root")
.withXPathNamespace("ns","http://www.namespace.com"))
.willReturn(aResponse()
.withStatus(200)
.withTransformers("my-transformer")
.withTransformerParameter("param", true)
.withTransformerParameter("responses", responsesMap)));
当请求match以下条件,url,xpath,namespace
则会返回一个response,状态码为200,包含一个Transformer
以下内容理解自https://dzone.com/articles/wiremock-and-response-transformer
我需要在测试类中实现:
首先发送x请求,然后返回y响应
然后发送x+1请求,返回z响应。
但是Wiremock并没有像mockito实现thenReturn功能,他提供了ResponseTransformer保存一个状态(state),默认情况下返回y,当x+1状态时返回z。
以下为transform定义
/**
* 实现:发送request,返回response,并在参数包含param==true时,在response中加入customerCar节点
* 继承ResponseTransformer
*/
class SuccessfulAtLastTransformer extends ResponseTransformer {
SuccessfulAtLastTransformer() {}
@Override
Response transform(Request request, Response response, FileSource files, Parameters parameters) {
return null
}
// 重写transform方法
@Override
ResponseDefinition transform(Request request, ResponseDefinition responseDefinition,
FileSource fileSource, Parameters parameters) {
// 判断当前请求是否为目标接口
if (request.getUrl().contains("/customer/cust1234")) {
// 判断是否包含目标请求参数
if (containsKey("param") && parameters.getBoolean("param")) {
def responseJson = new JsonSlurper().parseText(responseDefinition.body)
// 在responseJson中追加customerCar节点
responseJson.customerCar = "toyota"
def newRequestBody = JsonOutput.toJson(responseJson)
return new ResponseDefinitionBuilder()
.withStatus(200)
.withBody(newRequestBody)
.build()
}
// 否则直接返回原response
return new ResponseDefinitionBuilder()
.withStatus(200)
.withBody(responseDefinition.body)
.build()
}
return responseDefinition
}
// 设置transform不为globally的,否则会变成拦截器???
@Override
boolean applyGlobally() {
return false
}
// 定义transformer名称
@Override
String name() {
return "SuccessfulAtLastTransformer"
}
@Override
String getName() {
return null
}
}
以下为调用
stubFor(post(urlEqualTo("/customer/cust1234"))
.willReturn(aResponse()
.withStatus(200)
.withTransformers("SuccessfulAtLastTransformer")
.withTransformerParameter("param", true)
以上代码中,在执行“/customer/cust1234”请求时会拦截 ,当设置param为true时返回一个包含了customerCar 节点的200响应,否则返回普通200响应。
总结起来,transfomer的作用就是根据条件修改response,如何修改则在Transformer类中的重写transform方法中实现。
当待测试的代码需要访问多个service获取多个response用户后续逻辑时,就用到了Scenarios/States
或者,有条件的返回某些response,
关键方法
inScenario()
whenScenarioStateIs()
willSetStateTo()
@Test
public void toDoListScenario() {
// 当场景为To do list,状态为STARTED时返回response
stubFor(get(urlEqualTo("/todo/items")).inScenario("To do list")
.whenScenarioStateIs(STARTED)
.willReturn(aResponse()
.withBody("<items>" +
" <item>Buy milk</item>" +
"</items>")));
// 当场景为To do list,状态为STARTED时
// 发送post请求,并且request body包含Cancel newspaper subscription
// 返回response 201,并将status置为Cancel newspaper item added
stubFor(post(urlEqualTo("/todo/items")).inScenario("To do list")
.whenScenarioStateIs(STARTED)
.withRequestBody(containing("Cancel newspaper subscription"))
.willReturn(aResponse().withStatus(201))
.willSetStateTo("Cancel newspaper item added"));
// 当场景为To do list,状态为Cancel newspaper item added时
// 返回response Cancel newspaper subscription
stubFor(get(urlEqualTo("/todo/items")).inScenario("To do list")
.whenScenarioStateIs("Cancel newspaper item added")
.willReturn(aResponse()
.withBody("<items>" +
" <item>Buy milk</item>" +
" <item>Cancel newspaper subscription</item>" +
"</items>")));
// 发送第一个请求get,state为STARTED
WireMockResponse response = testClient.get("/todo/items");
// response为Buy milk,state仍为STARTED
assertThat(response.content(), containsString("Buy milk"));
assertThat(response.content(), not(containsString("Cancel newspaper subscription")));
// 发送第二个请求post,添加一条item
response = testClient.postWithBody("/todo/items", "Cancel newspaper subscription", "text/plain", "UTF-8");
// response为201,此时state为Cancel newspaper item added
assertThat(response.statusCode(), is(201));
// 发送第三个请求get,此时state为Cancel newspaper item added
response = testClient.get("/todo/items");
// 返回两条item记录
assertThat(response.content(), containsString("Buy milk"));
assertThat(response.content(), containsString("Cancel newspaper subscription"));
}
可以看到,Scenario/State是应用在同一场景,不同状态,不同response下使用。
刚才说了transformer可以修改response,但是多种情况下,写transform挨个定义response,太麻烦了,因此使用Scenario/State实现。