MockWebServer是一个有用的库,用于模拟当前组件(被测试)所依赖的API。这样的模拟API在微服务架构中是非常有用的,因为我们要同时开发多个依赖性服务。
在本教程中,我们将学习如何在JUnit 5测试中设置MockWebServer。我们将使用SpringWebClient作为HTTP客户端来调用模拟的API。
1.Maven的依赖性
MockWebServer是okhttp3库的一部分,所以我们需要导入以下两个依赖项。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.10.0</version>
<scope>test</scope>
</dependency>
2.启动和停止MockWebServer
我们可以使用MockWebServer,就像其他类似的库,如WireMock。通常情况下,我们
- 配置Mock
- 在测试开始前启动服务器
- 运行测试并验证响应
- 在测试结束后停止服务器
下面的例子使用@BeforeAll 和@AfterAll 钩子来启动和停止服务器。默认情况下,服务器在8080端口启动。我们可以通过在start() 方法中指定使用不同的端口。
public class MockWebServerTests {
public static MockWebServer server;
@BeforeAll
static void setUp() throws IOException {
server = new MockWebServer();
server.start(8080);
}
@AfterAll
static void tearDown() throws IOException {
server.shutdown();
}
}
如果我们想的话,我们可以为每个单元测试创建一个新的服务器实例。
public void test() throws Exception {
MockWebServer server = new MockWebServer();
server.start(8080);
//---test---
server.shutdown();
}
3.设置Mocks
3.1.使用Server.enqueue()
为了设置模拟,我们可以使用Server.enqueue() 方法。我们可以根据自己的需要来排队获取响应。然后我们可以点击模拟的API URL来依次获取模拟的响应。
public void test() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody("message 1"));
server.enqueue(new MockResponse().setBody("message 2"));
server.enqueue(new MockResponse().setBody("message 3"));
server.start();
//more code
}
3.2.使用Dispatcher
返回排队的响应并不是在所有情况下都适合。我们可以使用Dispatcher 类来设计我们自己的逻辑来返回API响应。
在下面的例子中,我们使用一个调度器来匹配传入请求的API URI,然后返回匹配URI的响应。
public class MockWebServerTests {
public static MockWebServer server;
final static Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
switch (request.getPath()) {
case "/api-url-one":
return new MockResponse()
.setResponseCode(201);
case "/api-url-two":
return new MockResponse()
.setHeader("x-header-name", "header-value")
.setResponseCode(200)
.setBody("<response />");
case "/api-url-three":
return new MockResponse()
.setResponseCode(500)
.setBodyDelay(5000, TimeUnit.SECONDS)
.setChunkedBody("<error-response />", 5);
case "/api-url-four":
return new MockResponse()
.setResponseCode(200)
.setBody("{\"data\":\"\"}")
.throttleBody(1024, 5, TimeUnit.SECONDS);
}
return new MockResponse().setResponseCode(404);
}
};
@BeforeAll
static void setUp() throws IOException {
server = new MockWebServer();
server.setDispatcher(dispatcher);
server.start(8080);
}
//more test code
}
4.编写测试
4.1.正常的JUnit测试
在设置好模拟后,我们可以使用Spring WebClient打出模拟的API。要获得API主机的URL,请使用server.getHostName() 方法。
WebClient webClient = WebClient
.create(String.format("http://%s:8080", server.getHostName()));
最后,点击模拟的API,并根据需要传递请求参数和主体。
Mono<String> apiResponse = webClient.post()
.uri("/api-url-two")
.body(Mono.just("<data />"), String.class)
.header("Authorization", "Basic " +
Base64Utils.encodeToString(("username:password").getBytes(UTF_8)))
.retrieve()
.bodyToMono(String.class);
一旦API响应可用,我们就可以投射Reactor的StepVerifier 来测试这些异步响应。
StepVerifier.create(apiResponse)
.expectNext("<response />")
.verifyComplete();
4.2.错误条件
API响应不会一直成功。我们可能会得到不同的错误代码和其他失败,如网络问题和延迟。MockWebServer支持这些类型的错误模拟响应。
例如,我们可以使用setBodyDelay() 方法测试超时逻辑和延迟响应。
new MockResponse()
.setResponseCode(200)
.setBodyDelay(5000, TimeUnit.MILLISECONDS)
.setBody("<data-response />");
为了测试缓慢的网络,我们可以使用setChunkedBody() 方法来分块发送响应。给定的模拟将分5个小块发送响应。
new MockResponse()
.setResponseCode(200)
.setChunkedBody("<data-response />", 5);
5.验证服务器统计信息
有时候,验证一个请求在模拟服务器上被击中了多少次是很重要的。这在我们实现和测试重试逻辑时特别有用。我们可以使用RecordedRequest实例来获取HTTP请求MockWebServer的详细信息,以确保我们的WebClient发送正确。
RecordedRequest request = server.takeRequest();
assertEquals("/api-url-two", request.getPath());
assertEquals("POST", request.getMethod());
assertNotNull(request.getHeader("Authorization"));
assertEquals("<data />", request.getBody().readUtf8());
6.结语
在本教程中,我们学习了如何使用MockWebServer来模拟API和响应,然后使用WebClient来消费这些API。
我们学会了启动和停止服务器、设置模拟、编写成功和错误测试、验证发送请求的细节等等。还有一些其他流行的替代品,你可以考虑,如WireMock。
学习愉快!!