Search code examples
javajerseyjunit5jersey-test-framework

JerseyExtension based test with JUnit5 not keeping session between requests


I have a self contained Jersey test using JerseyExtension (JerseyExtension) with JUnit5 (since JerseyTest does not work with JUnit5 unless you use the vintage engine) and subsequent calls to the container are getting different session. Is there a way to keep the session store same between the calls?

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));
    }
}

Sample output:

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

The session ID changed between the call to PUT and GET.


Solution

  • The client used by Jersey test framework, does not behave like a browser when it comes to Set-Cookie/Cookie headers. The two requests are not connected and JSESSIONID set by first response is not propagated to next request. While the framework is aware of the JSESSIONID if present, it does not span requests and needs to be manually copied forward.

    Changing the test method to following works:

    @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));
    }