如何使用JUnit模拟HTTP请求头与请求体进行单元测试
LoggingMethods 类单元测试实现方案
核心测试逻辑
两个方法均无返回值,核心逻辑是拼接请求参数后输出INFO级别日志,单元测试不需要启动完整Spring容器,核心验证点为传入指定请求参数时,日志输出内容符合预期。
前置准备
需要用到的测试组件:
- JUnit 5(Jupiter)作为测试运行框架
- Mockito 用于Mock
ServerHttpRequest接口实例,无需构造真实HTTP请求对象 - 基于项目自带的日志框架(默认Logback)实现内存日志捕获,无需额外引入第三方日志测试依赖
注意:
LoggingMethods的构造方法、两个成员方法均为包访问权限,测试类需要放置在和LoggingMethods相同的包路径下(test目录的对应包中),否则无法访问被测方法。
实现步骤
- Mock请求对象构造
利用Mockito直接MockServerHttpRequest接口,根据测试场景打桩返回对应值:- 测试
appendBodyRequest时:Mock请求头对象,塞入自定义测试头键值对;将测试用的请求体字符串转为UTF-8编码的ByteBuffer实例 - 测试
logRequestNoBody时:打桩返回指定的HTTP请求方法枚举值(如GET/POST)
- 测试
- 日志输出捕获
在测试类初始化阶段,给LoggingMethods类对应的Logger绑定一个内存日志追加器,把测试过程中该类输出的所有日志暂存到内存集合中,测试执行完成后移除追加器避免影响其他测试用例。 - 断言验证
调用被测方法后,读取内存中捕获的日志内容,断言日志包含预期拼接的参数值即可。
参考测试代码
package 替换为你项目中LoggingMethods所在的实际包名; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.ServerHttpRequest; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class LoggingMethodsTest { @Mock private ServerHttpRequest mockRequest; private ListAppender<ILoggingEvent> logAppender; private Logger classLogger; @BeforeEach void setUp() { // 初始化日志捕获器 classLogger = (Logger) LoggerFactory.getLogger(LoggingMethods.class); logAppender = new ListAppender<>(); logAppender.start(); classLogger.addAppender(logAppender); } @AfterEach void tearDown() { // 移除日志追加器,避免污染其他测试 classLogger.detachAppender(logAppender); } @Test void appendBodyRequest_shouldLogHeadersAndBody() { // 1. 构造测试参数 // 构造测试请求头 HttpHeaders testHeaders = new HttpHeaders(); testHeaders.add("Content-Type", "application/json"); testHeaders.add("X-Request-Id", "test-123456"); when(mockRequest.getHeaders()).thenReturn(testHeaders); // 构造测试请求体ByteBuffer String testBody = "{\"userId\":1,\"userName\":\"test\"}"; ByteBuffer testBodyBuffer = StandardCharsets.UTF_8.encode(testBody); // 2. 执行被测方法 LoggingMethods loggingMethods = new LoggingMethods(mockRequest); loggingMethods.appendBodyrequest(testBodyBuffer, mockRequest); // 3. 验证日志输出 assertEquals(1, logAppender.list.size()); ILoggingEvent logEvent = logAppender.list.get(0); // 验证日志包含预期的头信息 Map<String, String> loggedHeaders = (Map<String, String>) logEvent.getArgumentArray()[0]; assertEquals("application/json", loggedHeaders.get("Content-Type")); assertEquals("test-123456", loggedHeaders.get("X-Request-Id")); // 验证日志包含预期的请求体 String loggedBody = logEvent.getArgumentArray()[1].toString(); assertEquals(testBody, loggedBody); assertEquals("Headers:[{}] , Body:[{}]", logEvent.getMessage()); } @Test void logRequestNoBody_shouldLogMethodWithTip() { // 1. 构造测试参数 when(mockRequest.getMethod()).thenReturn(HttpMethod.POST); // 2. 执行被测方法 LoggingMethods loggingMethods = new LoggingMethods(mockRequest); loggingMethods.logRequestNoBody(mockRequest); // 3. 验证日志输出 assertEquals(1, logAppender.list.size()); ILoggingEvent logEvent = logAppender.list.get(0); assertEquals("{} response has no body.", logEvent.getMessage()); assertEquals(HttpMethod.POST, logEvent.getArgumentArray()[0]); } }
常见注意点
- 如果你的项目用的不是Logback而是其他日志框架,替换日志捕获的实现即可,核心思路是把日志输出重定向到可读取的内存容器中做断言
- Mock请求头时如果需要模拟多值头,直接调用
HttpHeaders的addAll方法塞入多值列表即可,toSingleValueMap会自动取第一个值输出 - 测试ByteBuffer时注意编码一致,必须和被测方法中使用的
StandardCharsets.UTF_8保持一致,避免中文乱码导致断言失败
内容的提问来源于stack exchange,提问作者sk001
相关产品推荐
相关产品推荐

