如何在 Spring Boot 应用程序中使用 WireMock 模拟外部 rest api 调用进行测试

模拟外部 API 调用是集成或端到端测试中的常见做法,因为它允许开发人员将他们的代码与外部隔离。如果我们使用付费 API 并希望避免在测试时进行调用以节省资金,这也会有所帮助。

有两种方法可以模拟外部 API

  1. 使用 Mockito
  2. 使用 WireMock

在集成测试和端到端测试中,我更喜欢使用 WireMock,因为使用 WireMock 我们也可以测试 http 交互,而 mockito 将模拟整个 http 调用方法。

我们用于说明如何使用 WireMock 的场景有两个微服务,分别称为订单服务和库存服务。订单服务中的下单端点将对库存服务的库存端点进行 http 调用,以检查产品是否有库存,如果库存充足,则创建订单。

订单服务 — 控制器

@RestController 
@RequestMapping("/api/order") 
@RequiredArgsConstructor 
public  class  OrderController { 
   private  final OrderService orderService; 
   @PostMapping 
   @ResponseStatus(HttpStatus.CREATED) 
   public String placeOrder ( @RequestBody OrderRequest orderRequest) { 
       orderService.placeOrder(orderRequest); 
       return  "订单下达成功" ; 
   } 
}

OrderService — 服务

@RequiredArgsConstructor 
@Service 
public  class  OrderService { 


   private  final OrderRepository orderRepository; 
   private  final InventoryClient inventoryClient; 
   public  void  placeOrder (OrderRequest orderRequest) { 
       var  isProductInStock  = inventoryClient.isInStock(orderRequest.skuCode(), orderRequest.quantity()); 
       if (!isProductInStock) {
          throw new RuntimeException ( "产品缺货 " +orderRequest.skuCode()+ " " ); 
       } 
       Order  order  =  new  Order (); 
       order.setOrderNumber(UUID.randomUUID().toString()); 
       order.setPrice(orderRequest.price()); 
       order.setSkuCode(orderRequest.skuCode()); 
       order.setQuantity(orderRequest.quantity()); 
       orderRepository.save(order); 
   } 
}

库存服务——控制器

@RestController 
@RequestMapping("/api/inventory") 
@RequiredArgsConstructor 
public  class  InventoryController { 
   private  final InventoryService inventoryService; 


   @GetMapping 
   @ResponseStatus(HttpStatus.OK) 
   public  boolean  isInStock ( @RequestParam String skuCode,@RequestParam Integer quantile) { 
       return inventoryService.isInStock(skuCode,quantity); 
   } 
}

Order 微服务中用于对 Inventory 微服务进行 rest api 调用的 feign 客户端接口如下(但我们也可以使用 rest 模板或新的 rest 客户端来代替 Feign 进行此 http 调用)

@FeignClient(value = "inventory-service", url = "${inventory.url}")
public interface InventoryClient {
   @RequestMapping(method = RequestMethod.GET, value = "/api/inventory")
   boolean isInStock(@RequestParam String skuCode, @RequestParam Integer quantity);
}

设置和使用 WireMock 的步骤如下;

  1. 将 WireMock 依赖项添加到 pom.xml

在依赖项下添加以下内容

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
   <scope>test</scope>
</dependency>

最后在 pom.xml 的属性部分下添加 <spring-cloud.version>2023.0.1</spring-cloud.version> ,如下所示

<properties>
   <java.version>21</java.version>
   <spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>

如果你使用 gradle,请将以下代码添加到你的 build.gradle 文件中

ext {
   set('springCloudVersion', "2023.0.1")set('springCloudVersion', "2023.0.1")
}


dependencies {
   implementation 'org.springframework.boot:spring-boot-starter'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
   testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
}


dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

2)使用@AutoConfigureWireMock注释spring boot测试

下一步是使用 @AutoConfigureWireMock 注释测试类

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
class OrderServiceApplicationTests {}

端口 0 表示我们要求 spring boot 使用随机端口,以避免端口冲突

3)创建一个存根客户端类,并在其中创建一个用于api调用的存根方法

我们需要为存根方法创建一个单独的类,并在其中定义用于 API 调用方法的存根。我将创建一个存根方法来调用库存服务中的以下端点 /api/inventory。

public class InventoryClientStub {
   public static void stubInventoryCall(String skuCode, Integer quantity) {
       System.out.println("Stubbing inventory call for skuCode: " + skuCode + " and quantity: " + quantity);
       stubFor(get(urlEqualTo("/api/inventory?skuCode=" + skuCode + "&quantity=" + quantity))
               .willReturn(aResponse()
                       .withStatus(200)
                       .withHeader("Content-Type", "application/json")
                       .withBody("true")));
   }
}

每当 WireMock 收到与请求参数“/api/inventory?skuCode=” + skuCode + “&quantity=” + amount 匹配的 URL 时,WireMock 将返回状态代码为 200 且主体为 JSON 值的响应

4)在测试目录中创建一个资源文件夹,并在资源目录中创建“application.properties”文件

然后在 test/resources/application.properties 中添加带有 Wiremock 动态端口的 url

5)最后将存根添加到测试方法中

将 InventoryClientStub.stubInventoryCall(“iphone_15”, 1) 存根添加到测试方法

@Test 
void  shouldPlaceOrder () { 
   String  requestBody  =  """ 
         { 
             "skuCode":"iphone_15", 
             "price": 1000, 
             "quantity": 1 
         } 
         """ ; 


   InventoryClientStub.stubInventoryCall( "iphone_15" , 1 ); 


   var  responseBodyString  = RestAssured.given() 
         .contentType( "application/json" ) 
         .body(requestBody) 
         .when() 
         .post( "/api/order" ) 
         .then() 
         .statusCode( 201 ) 
         .extract() 
         .body().asString(); 
   assertThat(responseBodyString, Matchers.is( "订单下单成功" )); 
}

就是这样。运行测试时,你将从 WireMock 获得以下日志

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值