Wiremock|Wiremock 入门教程1 - Spring reactor的简单集成测试

1.为什么使用Wiremock 我们在开发过程中, 有时会碰到需要测试其他系统响应的情况, 例如你开发的系统需要调用某个外部API, 此时, 为了快速开发测试, 我们需要模拟一个外部系统的请求及响应的过程, 那么此时Wiremock 就是一种选择
2.应用场景 场景1. 【Wiremock|Wiremock 入门教程1 - Spring reactor的简单集成测试】我们最常见的, 我们有时调用接口时需要获取认证中心提供的token再加入token到我们的请求中, 那么认证授权中心的服务在测试时我们可以用mock server来替代达到测试的效果.
场景2. 我们只对某请求结果二次封装, 内部实则是调用其他接口.
3.示例代码 3.1. maven依赖

4.0.0org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE com.example demo 0.0.1-SNAPSHOT demo Demo project for Spring Boot11 org.springframework.boot spring-boot-starter-webflux org.projectreactor reactor-spring 1.0.1.RELEASE org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine com.github.tomakehurst wiremock-jre8 2.25.1 test org.springframework.cloud spring-cloud-contract-wiremock 2.2.2.RELEASE test org.springframework.boot spring-boot-maven-plugin

3.2. 配置程服务器地址 也就是于application.yml中, 配置你依赖的API接口提供方
myserver: base_url: http://localhost:${wiremock.server.port}/

3.3. 业务代码 3.3.1. Controller
这里控制器本身没有特殊代码, 只是返回类型为reactor 的mono, 由于本例并不侧重reactor, 这里就不做延伸
@Slf4j @RestController @AllArgsConstructor @RequestMapping(path = "/test") public class DummyController {private final DummyService dummyService; @GetMapping(path = "/get/{id}") public Mono ping(@PathVariable("id") final String id) { return dummyService.pingById(id) .map(updatedId -> { log.info("get id from remote server: {}", updatedId); return ResponseEntity.ok(updatedId); }); }@PostMapping(path = "/post") public Mono submit(@RequestBody final String requestBody) { return dummyService.pingWithBody(requestBody) .map(res -> { log.info("response from remote server: {}", res); return ResponseEntity.ok(res); }); } }

3.3.2. Service
可以看到这里我们使用了reactor的 WebClient, 我们以这个client 来作为远端API的调用客户端 , 那么在本例中,由于我们使用的服务器是一个wiremock, 那么它将发送请求到mock的服务器获得虚拟的返回
@Slf4j @Service @AllArgsConstructor public class DummyService {private final WebClient webClient; private static final String REMOTE_GET_PATH = "/remote/get/{id}"; private static final String REMOTE_POST_PATH = "/remote/post"; public Mono pingById(String id) { log.info("processing id, {}", id); return webClient.get() .uri(REMOTE_GET_PATH, id) .retrieve() .bodyToMono(String.class); }public Mono pingWithBody(String bodyValue) { log.info("processing bodyValue, {}", bodyValue); return webClient.post() .uri(REMOTE_POST_PATH) .bodyValue(bodyValue) .retrieve() .bodyToMono(String.class); } }

3.3.3. Configuration
简单配置类, 获取配置的服务器地址信息
@ConfigurationProperties("myserver") @Validated @Data public class DummyConfiguration { @NotNull private String baseUrl; }

3.3.4. webclient 初始化
这里我们为了绑定service中的webclient 到配置的远端地址, 也就是我们定义的wiremock地址, 我们做了一个bean方便使用, 注意这里WebClient.Builder 是一个购置器入参.
@Component @AllArgsConstructor @ConfigurationPropertiesScan public class DummyManager {private final DummyConfiguration dummyConfiguration; @Bean WebClient webClient(WebClient.Builder webClientBuilder) { return webClientBuilder .baseUrl(dummyConfiguration.getBaseUrl()) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); } }

3.3.5. 启动类
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }

至此我们的代码就写完了, 那么如何模拟远端服务器的请求及响应呢? 我们接着看测试代码
3.4. 测试
@Slf4j @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = DemoApplication.class) //将wiremock初始化到spring context @AutoConfigureWireMock(port = 0) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class DummyControllerTest {//使用WebTestClient作为测试使用的服务端 private WebTestClient webTestClient; //获取端口 @LocalServerPort private int port; @BeforeAll void setup() { //初始化测试端client webTestClient = WebTestClient.bindToServer() .baseUrl("http://localhost:" + port) .responseTimeout(Duration.ofSeconds(10)) .build(); }@Test void testPing() { //这里便是wiremock的mock代码, 可以清楚看到, 我们mock 一个get请求, 返回一个123 并带有200 stubFor(get(urlMatching("/remote/.*")) .willReturn(aResponse() .withBody("123") .withStatus(200))); //测试我们自己的rest接口 webTestClient .get() .uri("/test/get/{id}", 123) .exchange() .expectStatus().isEqualTo(HttpStatus.OK) .expectBody(String.class) .consumeWith(stringEntityExchangeResult -> { // 验证 Assertions.assertEquals("123", stringEntityExchangeResult.getResponseBody()); }); }@Test void testSubmit() { String requestBody = "{\n" + "\"action\":\"post\",\n" + "\"value\":\"wiremock\"\n" + "}"; //mock post 请求 stubFor(post(urlMatching("/remote/post")) .willReturn(aResponse() .withBody(requestBody) .withStatus(200))); webTestClient .post() .uri("/test/post") .bodyValue("test") .exchange() .expectStatus().isEqualTo(HttpStatus.OK) .expectBody(String.class) .consumeWith(stringEntityExchangeResult -> { Assertions.assertEquals(requestBody, stringEntityExchangeResult.getResponseBody()); }); } }

5. 完整代码 https://github.com/wallisnow/wiremock_spring_reactor
6.结语 wiremock 可以帮助我们快速实现接口调用的测试, 代码量较小, 可以采用于自动化测试. 本例是一个入门案例并使用java代码的形式实现, 其本身也提供json配置的形式. 还有其他一些更深入的使用, 我们可以根据需要在官方文档中获得更多的信息, 例如一些其他常用的stub : http://wiremock.org/docs/stubbing/
7.Refs
  • wiremock 官方文档: http://wiremock.org/docs/
  • spring cloud: https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#features-wiremock

    推荐阅读