Servlet context injection fail while using jersey test framework
Asked Answered
N

3

10

I'm beginning with jersey and trying to get freemarker working with it using TDD. I want to make a ViewProcessor for my templates, but fail to inject the servlet context in the class.

Here is the class code :

@Provider
public class myProcessor implements ViewProcessor<Template> {

    [...]
   
    @Context
    public ServletContext myContext;

    [...]

 freemarkerConfiguration.setTemplateLoader(
       new WebappTemplateLoader(myContext,
           myContext.getInitParameter("freemarker.template.path")));

    [...]
    }

And here is the test code :

public class myProcessorTest extends JerseyTest {
   
    public static myProcessor mp;
   
    public myProcessorTest() throws Exception{
        super(new WebAppDescriptor.Builder("com.domain").build());
    }
   
    @Test
    public void firstTest(){
        mp = new myProcessor();
        String path = new String("test.ftl");
        Template template = mp.resolve(path);
        assertNotNull(template);
    }
}

I use maven with dependencies as follow:

<dependency>
    <groupId>com.sun.jersey.jersey-test-framework</groupId>
    <artifactId>jersey-test-framework-grizzly</artifactId>
    <version>1.5-SNAPSHOT</version>
    <scope>test</scope>
</dependency>

My code runs fine when I deploy to my local jetty server. But if I want to test the code in my IDE, it failed to inject the servlet context (@Context) : myContext is null when I run the test :/

I think I'm missing something, but I'm a complete beginner with servlet world.

Numberless answered 2/12, 2010 at 12:16 Comment(0)
D
2

Here's a technique for testing a specific resource class, using Jersey Test Framework, with servlet support. Also demonstrates how to customize the ServletContext.

import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.TestProperties;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;

import static org.mockito.Mockito.mock;

/**
 * A base class for testing web resources.
 */
public abstract class WebResourceTest extends JerseyTest {

    /**
     * Creates a JAX-RS resource configuration for test purposes.
     */
    @Override
    protected abstract ResourceConfig configure();

    /**
     * Creates a test container factory with servlet support.
     */
    @Override
    protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
        return new GrizzlyWebTestContainerFactory();
    }

    /**
     * Configures a deployment context for JAX-RS.
     */
    @Override
    protected DeploymentContext configureDeployment() {
        ResourceConfig app = configure();
        app.register(new Feature() {
            @Context
            ServletContext servletContext;

            @Override
            public boolean configure(FeatureContext context) {
                servletContext.setAttribute("example", new Object());
                return true;
            }
        });
        return ServletDeploymentContext.forServlet(new ServletContainer(app)).build();
    }
}

A usage example:

import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.core.Context;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.assertEquals;
import static org.junit.Assert.*;

public class MyResourceTest extends WebResourceTest {

    private MyResource resource;

    @Override
    protected ResourceConfig configure() {
        resource = spy(new MyResource());
        return new ResourceConfig().register(resource);
    }

    @Test
    public void testSomething() {
        Response r = target("/myresource").request().get();
        assertEquals(200, r.getStatus());
        assertEquals(1, resource.count);
    }
}

@Path("/myresource")
public class MyResource {
    int count = 0;

    @Context
    protected ServletContext servletContext;

    @GET
    public void get() {
       Object attr = servletContext.getAttribute("example");
       count++;
    }
}
Disciplinant answered 21/9, 2021 at 19:19 Comment(0)
P
0

There's a couple of ways to do it. Remove the constructor and implement a configure() method like this:

public class myProcessorTest extends JerseyTest {

    public static myProcessor mp;

    @Override
    protected AppDescriptor configure() {
    return new WebAppDescriptor.Builder("com.domain")
        .contextParam("contextConfigLocation", "classpath:/applicationContext.xml")
        .contextPath("/").servletClass(SpringServlet.class)
        .contextListenerClass(ContextLoaderListener.class)
        .requestListenerClass(RequestContextListener.class)
        .build();
  }

or alternatively you can annotate your test with the spring context:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyProcessorTest extends JerseyTest {

  public static myProcessor mp;
Pelargonium answered 12/2, 2014 at 0:32 Comment(0)
I
0

There is a solution to this problem that does not require spring, assuming you are using the default/standard Grizzy2 test framework provider. According to this answer the jersey-test-framework-provider-grizzly2 framework provider does not utilize a servlet environment in constructing the application context. Your symptoms result from there being no ServletContext instance to inject.

The workaround is to provide the test container for the unit tests yourself. First, modify your dependencies:

<!--<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.25</version>
    <scope>test</scope>
</dependency>-->
<dependency>
    <groupId>org.glassfish.jersey.test-framework</groupId>
    <artifactId>jersey-test-framework-core</artifactId>
    <version>2.25</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-grizzly2-servlet</artifactId>
    <version>2.25</version>
</dependency>

Then, modify your test to provide a Grizzy servlet container:

@Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
  return (final URI baseUri, final DeploymentContext deploymentContext) -> 
    new TestContainer() {
    private HttpServer server = null;

    @Override
    public ClientConfig getClientConfig() {
      return null;
    }

    @Override
    public URI getBaseUri() {
      return baseUri;
    }

    @Override
    public void start() {
      try {
        this.server = GrizzlyWebContainerFactory.create(baseUri, Collections
            .singletonMap("jersey.config.server.provider.packages", "<your-package-name>"));
      } catch (final ProcessingException | IOException cause) {
        throw new TestContainerException(cause);
      }
    }

    @Override
    public void stop() {
      this.server.shutdownNow();
    }
  };
}

I assume that you are going to use this in multiple unit tests, so it may be wise to extend JerseyTest so this common configuration may be performed automatically. Additionally, it may be worth reviewing org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory to see if there is any functionality provided by the test container that you wish to emulate/preserve. The example provided should be able to be dropped into your test to at least confirm this is a fix.

EDIT: In my own implementation, I required the ability to still supply a ResourceConfig when generating the server. I suspect that this is likely to be the common case for other Jersey Test Framework users. A working example of the proposed TestContainerFactory follows.

import java.io.IOException;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.UriBuilder;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.spi.TestContainer;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.glassfish.jersey.test.spi.TestHelper;


public class RestTestContainerFactory implements TestContainerFactory {      
  public static class RestTestContainer implements TestContainer {
    private static final Logger LOGGER = Logger.getLogger(RestTestContainer.class.getName());

    private URI baseUri = null;
    private final HttpServer server;

    public RestTestContainer(final URI baseUri, final DeploymentContext context) {
      this.baseUri = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
      if(LOGGER.isLoggable(Level.INFO)) {
        LOGGER.info("Creating RestRestContainer configured at the base URI "+TestHelper.zeroPortToAvailablePort(baseUri));
      }

      try {
        final WebappContext webContext = new WebappContext("TestContext", context.getContextPath());
        context.getResourceConfig()
               .register(new AbstractBinder() {
                @Override
                protected void configure() {
                  bind(webContext).to(ServletContext.class);
                }
              });
        this.server = GrizzlyHttpServerFactory.createHttpServer(this.baseUri, context.getResourceConfig(), false);
        webContext.deploy(this.server);

      } catch (final ProcessingException cause) {
        throw new TestContainerException(cause);
      }
    }

    @Override
    public ClientConfig getClientConfig() {
      return null;
    }

    @Override
    public URI getBaseUri() {
      return baseUri;
    }

    @Override
    public void start() {
      if(server.isStarted()) {
        LOGGER.warning("Ignoring start request - RestTestContainer is already started");
      } else {
        LOGGER.fine("Starting RestTestContainer...");
        try {
          server.start();
          if(baseUri.getPort() == 0) {
            baseUri = UriBuilder.fromUri(baseUri)
                .port(server.getListener("grizzly").getPort())
                .build();
            LOGGER.info("Started GrizzlyTestContainer at the base URI "+baseUri);
          }
        }
        catch(final ProcessingException | IOException cause) {
          throw new TestContainerException(cause);
        }
      }
    }

    @Override
    public void stop() {
      if(server.isStarted()) {
        LOGGER.fine("Stopping RestTestContainer...");
        server.shutdownNow();
      } else {
        LOGGER.warning("Ignoring stop request - RestTestContainer is already stopped");
      }
    }
  }

  @Override
  public TestContainer create(final URI baseUri, final DeploymentContext context) {
    return new RestTestContainer(baseUri,context);
  }
}

Frustratingly, grizzly's GrizzlyWebContainerFactory will provide a servlet context, but not configure with a resource config. Inversely, GrizzlyHttpServerFactory will configure an application with a ResourceConfig, but will not provide a web context.

We can work around this by creating the WebappContext (extends ServletContext) manually, configuring it, and then injecting it into the resource config by means of an AbstractBinder.

Iconoduly answered 3/2, 2017 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.