Skip to content

真实 HTTP 集成测试 ​

直接调用 Handler 方法无法验证端口、上下文、拦截器及响应编码。以下示例使用 JDK 21、JUnit Jupiter 和当前 nexus.io 版本 tio-boot,项目需配置 JUnit 5 执行器;不需要数据库或第三方账号。

java
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import nexus.io.context.BootConfiguration;
import nexus.io.context.Context;
import nexus.io.tio.boot.TioApplication;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.util.Resps;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class HttpIntegrationTest {
  static Context context;
  static HttpClient client;
  static final int PORT = Integer.getInteger("test.http.port", 18781);

  @BeforeAll
  static void start() {
    BootConfiguration configuration = () ->
      TioBootServer.me().getRequestRouter().add("/health", request -> {
        var response = Resps.json(request, Map.of("status", "ok"));
        if (!"GET".equals(request.getRequestLine().getMethod().toString())) {
          response = Resps.json(request, Map.of("error", "method"));
          response.setStatus(405);
        }
        return response;
      });
    context = TioApplication.run(configuration, new String[] {
      "--server.port=" + PORT, "--server.context-path=/test"
    });
    client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
  }

  @AfterAll
  static void stop() {
    try {
      if (client != null) client.close();
    } finally {
      if (context != null) context.close();
    }
  }

  @Test
  void externalPathAndResponse() throws Exception {
    var request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + PORT + "/test/health"))
      .timeout(Duration.ofSeconds(5)).GET().build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
    assertEquals(200, response.statusCode());
    assertTrue(text(response).contains("\"ok\""));
  }

  @Test
  void rejectsPost() throws Exception {
    var request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + PORT + "/test/health"))
      .timeout(Duration.ofSeconds(5)).POST(HttpRequest.BodyPublishers.noBody()).build();
    assertEquals(405, client.send(request, HttpResponse.BodyHandlers.discarding()).statusCode());
  }

  static String text(HttpResponse<byte[]> response) throws Exception {
    byte[] bytes = response.body();
    if (response.headers().firstValue("Content-Encoding").orElse("").equalsIgnoreCase("gzip")) {
      try (var gzip = new GZIPInputStream(new ByteArrayInputStream(bytes))) {
        bytes = gzip.readAllBytes();
      }
    }
    return new String(bytes, StandardCharsets.UTF_8);
  }
}

CI 应分配未占用端口,例如 PowerShell 使用 mvn test '-Dtest.http.port=18782'。绑定失败可能导致 JVM 退出,宜使用独立测试进程,避免同进程并行启动多个上下文。示例只注册测试 Handler,不加载生产业务配置。

业务项目还应验证匿名拒绝、失效 Token、跨用户/租户访问、错误 JSON 和方法限制。支付、短信等第三方未接入时应测试明确失败,不能使用万能验证码或伪造支付成功作为验证。

MIT Licensed