You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:05:04