1. 引言

在软件开发过程中,测试的重要性怎么强调都不为过。而谈到测试,模拟工具(Mocking Tools)往往是确保测试覆盖率的关键角色。

本文将探讨Moco,一个轻量级、多功能的模拟工具,能简化多种协议的模拟服务器搭建。

我们将深入解析Moco的核心功能、快速入门方法,并通过实战案例展示其强大能力。

2. Moco是什么?

Moco是一个开源的模拟库,专门用于服务桩(Stub)和模拟(Mock)。 它极其轻量,配置和使用都异常简单。

Moco支持多种协议,包括HTTP、HTTPS和Socket,使其成为测试各类应用(从Web服务到实时通信应用)的理想选择。

3. 快速上手Moco

Moco最大的优势之一就是极简的配置和使用流程。 无论是集成到新项目还是现有项目都非常直接。

3.1. JSON配置与独立服务器

在深入代码前,需要特别说明:Moco支持通过JSON编写配置并独立运行。这对快速搭建零代码的模拟环境特别有用。

以下是一个简单的JSON配置示例(保存为config.json):

[
  {
    "request": {
      "uri": "/hello"
    },
    "response": {
      "text": "Hello, Moco!"
    }
  }
]

使用该配置启动服务器只需运行moco-runner

java -jar moco-runner-1.5.0-standalone.jar http -p 12345 -c config.json

这条命令会在端口12345启动一个HTTP服务器,使用提供的config.json配置。模拟接口可通过http://localhost:12345/hello访问。

这种独立配置方式灵活性极高,且与代码配置享有同等的开发支持。

3.2. 项目集成

要在Maven项目中使用Moco,添加以下依赖

<dependency>
    <groupId>com.github.dreamhead</groupId>
    <artifactId>moco-runner</artifactId>
    <version>1.5.0</version>
    <scope>test</scope>
</dependency>

对于Gradle项目,使用以下配置:

testImplementation 'com.github.dreamhead:moco-runner:1.5.0'

4. Java实战案例

Moco提供了丰富的Java API,让我们在定义模拟资源时拥有极大自由度。 但我们先通过简单示例了解服务器初始化流程。

4.1. 服务器初始化

使用Java初始化Moco服务器时,需根据协议类型进行设置。快速概览如下:

HttpServer server = Moco.httpServer(12345); // 在12345端口初始化服务器
server.request(Moco.by(Moco.uri("/hello")))
  .response(Moco.text("Hello, Moco!")); // 设置基础响应
 
Runner runner = Runner.runner(server); 
runner.start(); // 启动服务器

本例中,我们先在端口12345初始化HTTP服务器。然后配置服务器:当收到*/hello接口请求时返回"Hello, Moco!"。最后通过Runner的start()*方法启动服务器。

⚠️ 别忘了在测试结束后停止服务器:

runner.stop();

例如在测试中,可将其放入带*@AfterEach注解的方法。对于HTTPS,需先创建证书(HttpsCertificate对象),再使用httpsServer()*方法:

HttpsCertificate certificate = certificate(pathResource("certificate.jks"), 
  "keystorePassword", "certPassword");
HttpsServer server = httpsServer(12346, certificate);

若要使用Socket连接,Moco提供了*socketServer()*:

final SocketServer server = socketServer(12347);

我们也可以在创建服务器时使用前述的JSON配置:

final HttpServer server = jsonHttpServer(12345, file("config.json"));

4.2. HTTP状态码与响应

掌握基础配置后,我们探索更复杂的场景。首先返回JSON响应

server.request(by(uri("/api/user")))
  .response(header("Content-Type", "application/json"))
  .response(json("{\"id\": 1, \"name\": \"Ryland Grace\", \"email\": \"ryland.grace@example.com\"}"));

若JSON存储在文件中,可直接提供文件路径

server.request(by(uri("/api/user")))
  .response(header("Content-Type", "application/json"))
  .response(Moco.file("src/test/resources/user.json"));

Moco默认返回HTTP 200状态码,但支持自定义状态码(如模拟错误响应):

server.request(Moco.match(Moco.uri("/unknown"))).response(Moco.status(404), Moco.text("Not Found"));

前述示例未指定HTTP方法(默认使用GET)。*模拟POST请求时,需用post()替代request()**。实际上,前例可显式使用get()*。

POST模拟示例:

server.post(by(uri("/resource"))).response("resource updated");

Moco为GET/POST/PUT/DELETE分别提供了get(), post(), put(), *delete()*方法。

此外,可指定请求内容进行匹配:

server.request(json(file("user.json"))).response("resource updated");

4.3. 高级定制

上述示例仅展示了Moco能力的冰山一角。实际上,Moco支持多种方式精细调优服务器。 例如配置查询参数、Cookie、媒体类型、自定义条件等。

以下示例使用JSONPath匹配请求

server.request(eq(jsonPath("$.item[*].price"), "0")).response("we have free item");

配置响应时,可模拟代理、重定向、文件附件、响应延迟、循环返回等。例如模拟状态随时间变化的资源:

// 首次请求返回"Alice",后续依次返回"Bob"、"Clyde",第四次循环回"Alice"
server.request(by(uri("/user"))).response(seq("Alice", "Bob", "Clyde")); 

5. 在单元测试中使用Moco

Moco能与JUnit无缝集成。 通过将Moco服务器嵌入测试生命周期,可有效模拟外部服务。基本用法如下:

public class MocoUnitTest {
    private Runner runner;

    @BeforeEach
    public void setup() {
        HttpServer server = Moco.httpServer(12345);
        server.request(Moco.by(Moco.uri("/test"))).response("Test response");
        runner = Runner.runner(server);
        runner.start();
    }

    @AfterEach
    public void tearDown() {
        runner.stop();
    }

    // 测试方法
}

在setup中,我们创建了一个HTTP服务器:当收到*/test*接口请求时返回"Test response"。服务器在每个测试前启动,测试后停止。

现在测试该服务器:

@Test
void givenMocoHttpServer_whenClientSendsRequest_thenShouldReturnExpectedResponse() throws Exception {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("http://localhost:12345/test"))
      .build();
    
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    assertEquals("Test response", response.body());
}

本例使用Java内置的HttpClient向Moco服务器发送请求。这种方式无需额外依赖,是单元测试中测试HTTP交互的绝佳选择。

5.1. 配合*@Rule*使用

在JUnit 4中,Moco通过TestRule简化集成。MocoJunitRunner提供多种方式将Moco服务器作为TestRule运行,自动管理生命周期:

public class MocoJunitHttpUnitTest {
    private static final HttpServer server = httpServer(12306);

    static {
        server.response(text("JUnit 4 Response"));
    }

    @Rule
    public MocoJunitRunner runner = MocoJunitRunner.httpRunner(server);

    @Test
    public void givenMocoServer_whenClientSendsRequest_thenShouldReturnExpectedResponse() throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create("http://localhost:12306"))
          .build();

        HttpResponse<String> response = client
          .send(request, HttpResponse.BodyHandlers.ofString());

        assertEquals(response.body(), "JUnit 4 Response");
    }
}

原始标题:How to Setup Stub Server Easily Using Moco | Baeldung