JUnit 5测试:如何从InvocationBuilder/Invocation获取自定义请求头
如何验证JAX-RS请求中添加的自定义请求头
我来帮你解决这个问题——你遇到的核心问题是混淆了请求头(你发送给API的内容)和响应头(API返回给你的内容),原测试代码里从builder.get()返回的Response中获取headers,拿到的是服务器返回的响应头,自然找不到你添加的x-app-id和x-app-key。
下面我给你两种可靠的解决方案,帮你验证发送出去的请求头:
方案一:用MockWebServer模拟外部API(推荐,贴近真实场景)
MockWebServer可以帮你启动一个本地的模拟服务器,捕获所有发送过来的请求,这样你就能直接检查请求里的头信息了。
步骤1:添加依赖
如果用Maven,在pom.xml里加入MockWebServer的测试依赖:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
步骤2:修改测试代码
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.given; import static org.mockito.Mockito.mock; class ProductsFacadeImplTest { private MockWebServer mockWebServer; private ProductsFacadeImpl facade; private NutritionixHeader mockNutritionixHeader; @BeforeEach void setUp() throws Exception { // 启动模拟服务器 mockWebServer = new MockWebServer(); mockWebServer.start(); // 模拟NutritionixHeader mockNutritionixHeader = mock(NutritionixHeader.class); // 创建自定义的Facade实例,替换API地址为模拟服务器地址 facade = new ProductsFacadeImpl(mockNutritionixHeader) { @Override public Invocation prepareHeaderForApiCall(String query) { Client client = ClientBuilder.newClient(); // 用模拟服务器的地址替换原API地址 String mockUrl = mockWebServer.url("/v2/search/instant?query=" + query).toString(); WebTarget webTarget = client.target(mockUrl); Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON_VALUE); invocationBuilder.header("x-app-id", nutritionixHeader.getNutritionixAppId()); invocationBuilder.header("x-app-key", nutritionixHeader.getNutritionixAppKey()); return invocationBuilder.buildGet(); } }; } @AfterEach void tearDown() throws Exception { // 关闭模拟服务器 mockWebServer.shutdown(); } @Test void prepareHeaderForApiCall_shouldAddCorrectHeaders() throws InterruptedException { // 测试数据 String testQuery = "apple"; String testAppId = "my-test-app-id"; String testAppKey = "my-test-app-key"; // 设置mock返回值 given(mockNutritionixHeader.getNutritionixAppId()).willReturn(testAppId); given(mockNutritionixHeader.getNutritionixAppKey()).willReturn(testAppKey); // 执行请求 Invocation invocation = facade.prepareHeaderForApiCall(testQuery); invocation.invoke(); // 捕获发送的请求 RecordedRequest capturedRequest = mockWebServer.takeRequest(); // 验证请求头是否正确 assertEquals(testAppId, capturedRequest.getHeader("x-app-id")); assertEquals(testAppKey, capturedRequest.getHeader("x-app-key")); } }
方案二:用JAX-RS ClientFilter拦截请求
如果你不想启动模拟服务器,可以用JAX-RS的ClientRequestFilter来拦截请求,记录下请求头信息后再验证。
步骤1:创建自定义拦截器
import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; class HeaderCapturingFilter implements ClientRequestFilter { private MultivaluedMap<String, String> capturedHeaders; @Override public void filter(ClientRequestContext requestContext) throws IOException { // 复制请求头到本地变量 capturedHeaders = new MultivaluedHashMap<>(requestContext.getHeaders()); } public MultivaluedMap<String, String> getCapturedHeaders() { return capturedHeaders; } }
步骤2:修改测试代码
首先,建议你给ProductsFacadeImpl添加一个可重写的创建Client的方法,方便测试替换:
@Service @AllArgsConstructor class ProductsFacadeImpl implements ProductsFacade { private NutritionixHeader nutritionixHeader; // 抽离Client创建逻辑,方便测试替换 protected Client createClient() { return ClientBuilder.newClient(); } @Override public Invocation prepareHeaderForApiCall(String query) { Client client = createClient(); WebTarget webTarget = client.target("https://trackapi.nutritionix.com/v2/search/instant?query=" + query); Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON_VALUE); invocationBuilder.header("x-app-id", nutritionixHeader.getNutritionixAppId()); invocationBuilder.header("x-app-key", nutritionixHeader.getNutritionixAppKey()); return invocationBuilder.buildGet(); } }
然后编写测试:
import org.junit.jupiter.api.Test; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; import javax.ws.rs.core.MultivaluedMap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.given; import static org.mockito.Mockito.mock; class ProductsFacadeImplTest { @Test void prepareHeaderForApiCall_shouldAddCorrectHeaders() { // 测试数据 String testQuery = "banana"; String testAppId = "test-id-123"; String testAppKey = "test-key-456"; // 模拟NutritionixHeader NutritionixHeader mockHeader = mock(NutritionixHeader.class); given(mockHeader.getNutritionixAppId()).willReturn(testAppId); given(mockHeader.getNutritionixAppKey()).willReturn(testAppKey); // 创建头拦截器 HeaderCapturingFilter headerFilter = new HeaderCapturingFilter(); // 创建自定义Facade实例,注册拦截器 ProductsFacadeImpl facade = new ProductsFacadeImpl(mockHeader) { @Override protected Client createClient() { return ClientBuilder.newClient().register(headerFilter); } }; // 执行请求(即使请求失败也没关系,我们只需要拦截头) Invocation invocation = facade.prepareHeaderForApiCall(testQuery); try { invocation.invoke(); } catch (Exception ignored) {} // 验证捕获的请求头 MultivaluedMap<String, String> capturedHeaders = headerFilter.getCapturedHeaders(); assertEquals(testAppId, capturedHeaders.getFirst("x-app-id")); assertEquals(testAppKey, capturedHeaders.getFirst("x-app-key")); } }
为什么原代码拿不到头?
你调试时看到的requestContext就是JAX-RS请求的上下文对象,里面包含了所有要发送的请求信息(包括头)。但原测试代码里调用builder.get()后,拿到的是Response对象,它的getHeaders()返回的是服务器返回的响应头,不是你发送的请求头,所以自然找不到你添加的自定义头。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

