JUnit5 基于 JerseyExtension 的测试不保持请求之间的会话

JerseyExtension based test with JUnit5 not keeping session between requests

我有一个独立的 Jersey 测试,它使用 JerseyExtension (JerseyExtension) 和 JUnit5(因为 JerseyTest 不能与 JUnit5 一起工作,除非你使用老式引擎)并且随后对容器的调用得到不同的会话。有没有办法在调用之间保持会话存储相同?

package com.test.jerseysession;

import com.github.hanleyt.JerseyExtension;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import static org.junit.jupiter.api.Assertions.assertNotNull;

public class JerseyTestWithGrizzly {
    private final static TestContainerFactory testContainerFactory;
    private final ServletContainer servletContainer;
    private final ResourceConfig resourceConfig;
    private final DeploymentContext deploymentContext;

    static {
        testContainerFactory = new GrizzlyWebTestContainerFactory();
    }

    @RegisterExtension
    @SuppressWarnings("unused")
    JerseyExtension jerseyExtension = new JerseyExtension(
            this::getTestContainerFactory,
            this::configureDeploymentContext,
            this::configureJerseyClient);

    public JerseyTestWithGrizzly() {
        this.resourceConfig = new ResourceConfig()
                .packages("com.test.jerseysession")
                .register(getClass());

        this.servletContainer = new ServletContainer(resourceConfig);

        this.deploymentContext = ServletDeploymentContext.builder(resourceConfig)
                .servlet(servletContainer)
                .servletPath("api")
                .build();
    }    

    @Path("session")
    public static class SessionResource {
        @GET
        public String get(@Context HttpServletRequest request) {
            HttpSession session = request.getSession(true);
            Object obj = session.getAttribute("name");
            return session.getId() + ": " + obj;
        }

        @PUT
        public String put(@Context HttpServletRequest request) {
            HttpSession session = request.getSession(true);
            session.setAttribute("name", "foo");
            return session.getId()+": Set name attribute called";
        }
    }

    protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
        assertNotNull(extensionContext);
        assertNotNull(clientConfig);
        return clientConfig;
    }

    protected DeploymentContext configureDeploymentContext(ExtensionContext extensionContext) {
        assertNotNull(extensionContext);
        return deploymentContext;
    }

    protected TestContainerFactory getTestContainerFactory(ExtensionContext extensionContext) {
        assertNotNull(extensionContext);
        return testContainerFactory;
    }

    @Test
    public void testSessionSet(WebTarget target) {
        // Call PUT which sets attribute called 'name'
        Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
        System.out.println("PUT:  status="+response0.getStatus()+" response="+response0.readEntity(String.class));

        // Call GET which should be able to find 'name' in session set by previous call
        Response response1 = target.path("session").request().get();
        System.out.println("GET:  status="+response1.getStatus()+" response="+response1.readEntity(String.class));
    }
}

示例输出:

PUT:  status=200 response=8373522406385125383: Set name attribute called
GET:  status=200 response=8264425692811867393: null

会话 ID 在 PUT 和 GET 调用之间发生了变化。

Jersey 测试框架使用的客户端在 Set-Cookie/Cookie headers 时表现得不像浏览器。这两个请求未连接,第一个响应设置的 JSESSIONID 不会传播到下一个请求。虽然框架知道 JSESSIONID(如果存在),但它不跨越请求,需要手动向前复制。

将测试方法更改为以下工作:

@Test
public void testSessionSet(WebTarget target) {
    Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
    System.out.println("PUT:  status="+response0.getStatus()+" response="+response0.readEntity(String.class));

    Invocation.Builder nextRequestBuilder = target.path("session").request();
    NewCookie jsessionid = response0.getCookies().get("JSESSIONID");
    if (jsessionid != null) {
        nextRequestBuilder.cookie(jsessionid);
    }
    Response response1 = nextRequestBuilder.get();
    System.out.println("GET:  status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}