java -jar wiremock-standalone-2.11.0.jar
{
"request" : {
"urlPath" : "/api/order/find",
"method" : "GET",
"queryParameters" : {
"orderId" : {
"matches" : "^[0-9]{16}$"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "body-order-find-1.json",
"headers" : {
"Content-Type" : "application/json;charset=UTF-8"
}
}
}
{
"success": true,
"data": {
"id": 781202,
"buyerId": -2,
"status": 0,
// 略...
}
}
$ curl http://localhost:8080/api/order/find?orderId=abcdefghijklmnop
Request was not matched
=======================
----------------------------------------------
| Closest stub | Request
----------------------------------------------
GET | GET
/api/order/find | /api/order/find
----------------------------------------------
$ curl http://localhost:8080/api/order/find?orderId=9999999999999999
{
"success": true,
"data": {
"id": 781202,
"buyerId": -2,
"status": 0
}
}
通过 RESTFul 的接口提交和管理请求映射和相应。
支持响应模板,返回内容时会将变量填充到响应模板中。当然,这里的模板功能是比较简单的,但对于大部分 Stub 的场景应该是足够了。
支持模拟异常返回,例如设置有一定比例的超时返回等等,这个功能用于测试非常方便。
public class OrderTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule(9090);
/**
* 统一下单 Stub
* 参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
*
* @param tradeType 交易类型,可以是JSAPI、NATIVE或APP
*/
public void stubForUnifiedOrderSuccess(String tradeType) {
String unifiedOrderResp = "<xml>\n" +
" <return_code><![CDATA[SUCCESS]]></return_code>\n" +
" <return_msg><![CDATA[OK]]></return_msg>\n" +
" <appid><![CDATA[wxxxxxxxxxxxxxxxxx]]></appid>\n" +
" <mch_id><![CDATA[9999999999]]></mch_id>\n" +
" ...... \n" +
" <trade_type><![CDATA[" + tradeType + "]]></trade_type>\n" +
"</xml>";
stubFor(post(urlEqualTo("/pay/unifiedorder"))
.withHeader("Content-Type", equalTo("text/xml;charset=UTF-8"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody(unifiedOrderResp)));
}
@Test
public void test001_doPay() {
stubForUnifiedOrderSuccess("JSAPI");
payServices.pay();
// 测试代码...
}
}
verify(postRequestedFor(urlEqualTo("/pay/unifiedorder"))
.withHeader("Content-Type", equalTo("text/xml;charset=UTF-8"))
.withQueryParam("param", equalTo("param1"))
.withRequestBody(containing("success"));
作为代理运行,此时可以录制请求和返回的脚本,用于后继 Stub 和 Mock 使用。
独立运行,作为一个 Stub 服务,根据匹配的请求返回数据。
作为 Stub,通过代码嵌入 HTTP 模拟服务,在指定端口监听,并根据匹配的请求返回数据。
作为 Mock,在单元测试和集成测试中,验证请求逻辑。例如是否进行了调用、参数是否正确等。
在外部服务尚未开发完成时,模拟服务,方便开发。
在本地开发时,模拟外部服务避免直接依赖。
在单元测试中模拟外部服务,同时验证业务逻辑。
org.springframework.cloud.contract.spec.Contract.make {
// 如果消费方发送了一个请求
request {
// 请求方法是 POST
method 'POST'
// 请求 URL 是 `/sendsms`
url '/sendsms'
// 请求内容是 Json 文本,包括电话号码和要发送的文本
body([
// 电话号码必须是13个数字组成
phone: $(regex('[0-9]{13}')),
// 发送文本必须为"您好"
content: "您好"
])
}
response {
// 那么服务方应该返回状态码 200
status 200
// 响应内容是 Json 文本,内容为 { "success": true }
body([
success: true
])
}}
业务方和服务方相关人员一起讨论。业务方告知服务方接口使用的场景、期望的返回是什么,服务方考虑接口方案和实现,双方一起定下一个或多个契约。
确定了契约之后,Spring Cloud Contract 会给服务方自动生成验收测试,用于验证接口是否符合契约。服务方要确保开发完成后,这些验收测试都能够通过。
业务方也可以基于这个契约开始开发功能。Spring Cloud Contract 会基于契约生成 Stub 服务,这样业务方就不必等接口开发完成,可以通过 Stub 服务进行集成测试。
让服务方和调用方有充分的沟通,确保服务方提供接口都是以调用方的需求出发,并且服务方的开发者也可以充分理解调用方的使用场景。
解耦和服务方和调用方的开发过程,一旦契约订立,双方都可以并行开发,通过 Mock 和自动化集成测试确保双方都遵守契约,最终集成也会更简单。
通过 Mock 和自动化测试,可以确保双方在演进过程中,也不会破坏已有的契约。
./gradlew generateContractTests
public class SmsTest extends ContractBase {
@Test
public void validate_sendsms() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.body("{\"phone\":\"2066260255168\",\"content\":\"\u60A8\u597D\"}");
// when:
ResponseOptions response = given().spec(request)
.post("/sendsms");
// then:
assertThat(response.statusCode()).isEqualTo(200);
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['success']").isEqualTo(true);
}
}
public void setup() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
./gradlew verifierStubsJar
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
@AutoConfigureStubRunner(repositoryRoot="http://<nexus_root>",
ids = {"com.xingren.service:sms-client-stubs:1.5.0-SNAPSHOT:stubs:6565"})
public class ContractTest {
@Test
public void testSendSms() {
ResponseEntity<SmsServiceResponse> response =
restTemplate.exchange("http://localhost:6565/sendsms", HttpMethod.POST,
new HttpEntity<>(request), SmsServiceResponse.class);
// do some verification
}
}
$ brew tap pivotal/tap
$ brew install springboot
$ spring install org.springframework.cloud:spring-cloud-cli:1.4.0.RELEASE
stubrunner:
workOffline: false
repositoryRoot: http://<nexus_root>
ids:
- com.xingren.service:sms-client-stubs:1.5.0-SNAPSHOT:stubs:6565
org.springframework.cloud.contract.spec.Contract.make {
request {
method('GET')
url $(consumer(~/\/[0-9]{2}/), producer('/12'))
}
response {
status 200
body(
name: $(consumer('Kowalsky'), producer(regex('[a-zA-Z]+')))
)
}
}
本次培训包含:Kubernetes核心概念;Kubernetes集群的安装配置、运维管理、架构规划;Kubernetes组件、监控、网络;针对于Kubernetes API接口的二次开发;DevOps基本理念;微服务架构;微服务的容器化等,点击识别下方二维码加微信好友了解具体培训内容。