How to start a Jersey Test Container (Grizzly) onc

2020-07-11 07:45发布

问题:

I am working on fixing the integration tests in one of the projects. Currently, all the integration test classes extend the JerseyTest class. Going through the JerseyTest class I realized that it starts and stops the container for every test method using Junit's Before and After annotations.

Why is this necessary? Isn't it enough if we bring up the container once, run the tests and shut it down at the end of it?

We also use Spring and it takes time for the context to get initialized.

Prior to Junit4 we worked around this limitation by handling it manually using boolean flags.

@Before
public void setup() {
  if(!containerStarted) {
   // start
   containerStarted = true;    
  }
 // else do nothing
}

回答1:

You can use @BeforeClass and @AfterClass to override JerseyTest's @Before and @After lifecycle. Here is the code template:

public abstract class MyJerseyTest {
  private JerseyTest jerseyTest;
  public MyJerseyTest(){

  }

  @BeforeClass
  public void setUp() throws Exception {
    initJerseyTest() 
    jerseyTest.setUp();
  }

  @AfterClass
  public void tearDown() throws Exception {
    jerseyTest.tearDown();
  }

  private void initJerseyTest() {
    jerseyTest = new JerseyTest() {
      @Override
      protected Application configure() {
        // do somthing like
        enable(...);
        set(...);

        // create ResourceConfig instance
        ResourceConfig rc = new ResourceConfig();

        // do somthing like
        rc.property(...);
        rc.register(...);

        return rc;
      }
    };
  }
}

Hope this is helpful.



回答2:

We had a similar situations, using jersey plus spring as dependency injection framework (jersey-spring-bridge). Writing integration test with JerseyTest framework is tricky because it starts container before a test and stops the container after the test. This approach might have an advantage but, its very time consuming and tricky taking into account the fact that spring does scanning and autowiring of beans everytime.

How to initialize a grizzly container once and use it for all tests in a test class?

To achieve the above, I followed the following steps:

In the test class as instance variable, declare an instance of HttpServer as follow

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class OrderResourceTest {
    ...
    public static final String BASE_URI = "http://localhost:8989/";
    private static HttpServer server = null;
    ...
    ...
}

Notice that I do not use JerseyTest because I want to handle the start/stop of test container myself. Now, you need to make use of @Before and @AfterClass to setup the server instance. In @Before we will setup the server instance such that it load our custom filter/listener definitions in the web.xml (like programmatically loading the web.xml into server instance)

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

        WebappContext ctx = new WebappContext() {};
        ctx.addContextInitParameter("contextConfigLocation", "classpath:applicationContext.xml");

        ctx.addListener("com.package.something.AServletContextListener");

        server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
            ctx.deploy(server);
        }
    }

If you notice about I am using @Before but, the if condition will make it function as @BeforeClass. Don't exactly remember why I did not use @BeforeClass and my guess would be probably due to some of those configurations inside the if block. Anyway, give it a try if you are curious.

  1. Create ResourceConfig with Resources that are going to be tested, this includes your Resource/Controller, ExceptionMapper if any, any other class that should be loaded.
  2. Create an instance of WebappContext and then programmatically add to it all the web.xml context init params such as applicationContext for example. Where applicationContext contains spring-based configs.
  3. If you have a listener in your web.xml, then you need to add them programmatically as shown above
  4. If you have any filters or something else then you need to programmatically add them to ctx.
  5. Initialize server with Grizzly instance by providing it URI and instance of ResourceConfig
  6. You have already programmatically created a WebappContext which is nothing but, web.xml and now use it's deploy method to pass the server instance to it. This will run the WebappContext configs and deploy it to server instance.

And now you have a test running an instance of Grizzly with your web.xml plus spring-specific configs applied to the instance.

The @AfterClass could look as follow

@AfterClass
    public static void tearDown() throws Exception {
        System.out.println("tearDown called ...");
        if (server != null && server.isStarted()) {
            System.out.println("Shutting down the initialized Grizzly instance");
            server.shutdownNow();
        }
    }

And a sample test using REST-assured framework

@Test
public void testGetAllOrderrs() { 
    List<Orders> orders= (List<Orders>) 
    when().
        get("/orders").
    then().
        statusCode(200).
    extract().
        response().body().as(List.class);

    assertThat(orders.size()).isGreaterThan(0);
}

The reason above works without specifying the base-path is because I set the REST-assured base path as follow

RestAssured.baseURI = "http://localhost:8989/";

If you didn't want to use REST-assured then

@Test
public void testGetAllOrders() {
    Client client = ClientBuilder.newBuilder().newClient();
    WebTarget target = client.target(BASE_URI);
    Response response = target
            .path("/orders")
            .request(MediaType.APPLICATION_JSON)
            .get();

    assertThat(response.getStatus()).isEqualTo(200);

    JSONArray result = new JSONArray(response.readEntity(String.class));

    assertThat(result.length()).isGreaterThan(0);
}

Jersey-based imports

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