Wiremock

参考链接:http://wiremock.org/docs/getting-started/

  1. 功能
  2. 例子
  3. Transform
  4. Scenarios/States

功能

在集成测试中模拟外部服务,即当系统需要通过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

Transform

以下内容理解自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方法中实现。

Scenarios/States

当待测试的代码需要访问多个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("" +
                "   Buy milk" +
                "")));
        
		// 当场景为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("" +
                "   Buy milk" +
                "   Cancel newspaper subscription" +
                "")));

        // 发送第一个请求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实现。

Tips

  1. 为了让所有的测试方法都能使用预设好的response,建议在@Before方法中执行stubFor();

你可能感兴趣的:(java)