简体   繁体   中英

Integration test of Jersey web services using Spring for dependency injection fails to initialize @Context HttpServletRequest

I have jersey web services that uses spring for dependency injection (spring-jersey module) and hibernate for object-relational mapping (ORM). In order to develop integration tests taking into account the following conditions:

  1. Intiailize the test container only once for entire test class
  2. Register custom listeners, filters, servlets etc to the test container
  3. Make sure that @Context HttpServletRequest is not null

According to this https://java.net/jira/browse/JERSEY-2412 Jersey project JIRA task HttpServletRequest is null and that's as shown in the resolution of the task Works as desgined . When running integration tests on Grizzly container, it runs the integration tests on http server hence, any dependence on servlet-based features such as HttpServletRequest, HttpServletResponse, etc. is not available.

There seems to be no standard solution on how to address this issue, and Jersey community is obviously open to such features being developed by contributors as stated in this https://java.net/jira/browse/JERSEY-2417 JIRA ticket. Until this feature is implemented, what are the possible workaround solutions? Based on my research I come cross a few posts that state:

  1. Use external container (What if I don't want too?)
  2. Use jersey's jetty module (What if I don't want to use Jetty?)
  3. SpringMVC specific solutions that do not apply to this project (for we don't use Spring MVC)

So, what's the best solution to successfully run integration tests on jersey-based web servers that uses spring-jersey bridge for dependency injection and rely on Servlet-based features?

This is standard behaviour from Jersey and the to allow servlet-based features such as HttpServletRequest is not available yet. Based on my research, I could achieve the following conditions

  1. Intiailize the test container only once for entire test class
  2. Register custom listeners, filters, servlets etc to the test container
  3. Make sure that @Context HttpServletRequest is not null

By starting/stopping A grizzly container manually and deploying the grizzly container instance on a custom jersey-based WebappContext. The steps are as follow in case anyone else come across such issue

  1. Create a HttpServer
  2. In @Before create a custom WebappContext which reflects your web.xml and deploy your HttpServer instance using the WebappContext
  3. In order to make sure that Servlet-based features such as HttpServletRequest are available during integration test use the ServletRegistration to load your application inside A Servlet container as shown below

Step #1

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class ResourceEndpointIntegrationTest{
    @Context
    private HttpServletRequest httpReq;

    private static Logger logger = Logger.getLogger(ResourceEndpointIntegrationTest.class);

    public static final String BASE_URI = "http://localhost:8989/";
    private static HttpServer server = null;

    @BeforeClass
    public static void initTest() {
        RestAssured.baseURI = "http://localhost:8989/";
    }
...
}

Use SpringJUnit4ClassRunner.class and @ContextConfiguration to load the applicationContext.xml for tests. Also declare @Context HttpServletRequest and create an instance of HttpServer to be used later. I use @BeforeClass here for Rest-Assured specific purposes (you don't have to use it), its optional.

Step #2

@Before
    public void setup() throws Exception {
        if (server == null) {
            System.out.println("Initializing an instance of Grizzly Container ...");
            final ResourceConfig rc = new ResourceConfig(ResourceEndpointIntegrationTest.class, ..., ..., ...); //update

            WebappContext ctx = new WebappContext("IntegrationTestContext");
                        //register your listeners from web.xml in here
            ctx.addListener("com.xxx.yyy.XEndpointServletContextListener");
                        //register your applicationContext.xml here
            ctx.addContextInitParameter("contextConfigLocation", "classpath:applicationContext.xml");

                        //ServletRegistration is needed to load the ResourceConfig rc inside ServletContainer or you will have no 
                        //Servlet-based features available 
            ServletRegistration registration = ctx.addServlet("ServletContainer",
                    new ServletContainer(rc));

                        //Initialize the Grizzly server passing it base URL
            server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI));

                        //Deploy the server using our custom context
            ctx.deploy(server);
        }
    }

The setup in @Before is run for every test but, the if condition will force the setup method to act like @BeforeClass allowing us to initialize the server once for the entire test class minus the static nature of @BeforeClass .

The reason I initialize the server once for all tests inside a test class is because, if we don't then we will have the following workflow

  1. A server is started
  2. Spring starts scanning for beans
  3. Spring does autowired
  4. Spring setups sessionFactory, etc.
  5. The test is run
  6. Spring destroys the context
  7. Server is shutdown
  8. Repeat step #1 to #7 for every test

Above is very time consuming and technically not feasible, hence initialize container once (which is why I don't extend JerseyTest because I want to control the startup/shutdown of the test container).

#Step 3

@AfterClass
    public static void tearDown() throws Exception {
        System.out.println("Integration tests completed. Tear down the server ...");
        if (server != null && server.isStarted()) {
            server.shutdownNow();
            System.out.println("Grizzly instance shutdown completed");
        }
    }

In step 3 we use @AfterClass to shutdown the grizzly instance used for integration testing purposes.

And a sample test looks as follow

@Test
    public void testCreateSomethingReturnSuccessfully() {
        JSONObject something = new JSONObject();
        cust.put("name", "integrationTest");
        cust.put("age", 33);

        given().
            contentType(ContentType.JSON).
            body(something.toString()).post("/someEndpoint").
        then().
            statusCode(200).
        assertThat().
            body("id", greaterThan(0)).
            body("name", equalTo("integrationTest")).
            body("age", equalTo(33));
    }

(Gradle) Some of the relevent depdendencies

compile group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: '2.23.2'
compile group: 'org.glassfish.jersey.test-framework.providers', name:'jersey-test-framework-provider-grizzly2', version:'2.23.2'
compile group: 'org.springframework', name:'spring-test', version:'4.3.2.RELEASE'
compile group: 'io.rest-assured', name:'rest-assured', version:'3.0.1'


// Spring
    compile group: 'org.springframework', name: 'spring-core', version: '4.3.2.RELEASE'
    compile group: 'org.springframework', name: 'spring-beans', version: '4.3.2.RELEASE'
    compile group: 'org.springframework', name: 'spring-web', version: '4.3.2.RELEASE'
    compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.2.RELEASE'
    compile group: 'org.springframework', name: 'spring-orm', version: '4.3.2.RELEASE'

    // Jersey-Spring bridge
    compile (group: 'org.glassfish.jersey.ext', name: 'jersey-spring3', version: '2.23.2'){
        exclude group: 'org.springframework', module: 'spring-core'
        exclude group: 'org.springframework', module: 'spring-web'
        exclude group: 'org.springframework', module: 'spring-beans'
        exclude group: 'org.springframework', module: 'spring-jdbc'
        exclude group: 'org.springframework', module: 'spring-orm'
    }

Some of the imports

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.servlet.ServletRegistration;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM