模拟外部 API 调用是集成或端到端测试中的常见做法,因为它允许开发人员将他们的代码与外部隔离。如果我们使用付费 API 并希望避免在测试时进行调用以节省资金,这也会有所帮助。
有两种方法可以模拟外部 API
- 使用 Mockito
- 使用 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 的步骤如下;
- 将 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 获得以下日志
标签:WireMock,skuCode,Spring,Boot,api,orderRequest,public,quantity From: https://blog.csdn.net/adminmail/article/details/144509745