真实 HTTP 集成测试
直接调用 Handler 方法无法验证端口、上下文、拦截器及响应编码。以下示例使用 JDK 21、JUnit Jupiter 和当前 nexus.io 版本 tio-boot,项目需配置 JUnit 5 执行器;不需要数据库或第三方账号。
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 和方法限制。支付、短信等第三方未接入时应测试明确失败,不能使用万能验证码或伪造支付成功作为验证。
