使用 jersey 测试框架时 Servlet 上下文注入失败
Posted
技术标签:
【中文标题】使用 jersey 测试框架时 Servlet 上下文注入失败【英文标题】:Servlet context injection fail while using jersey test framework 【发布时间】:2011-05-19 02:30:45 【问题描述】:我从 jersey 开始,并试图让 freemarker 使用 TDD 与它一起工作。我想为我的模板创建一个ViewProcessor
,但未能在类中注入 servlet 上下文。
这是课程代码:
@Provider
public class myProcessor implements ViewProcessor<Template>
[...]
@Context
public ServletContext myContext;
[...]
freemarkerConfiguration.setTemplateLoader(
new WebappTemplateLoader(myContext,
myContext.getInitParameter("freemarker.template.path")));
[...]
这是测试代码:
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);
我使用maven的依赖如下:
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly</artifactId>
<version>1.5-SNAPSHOT</version>
<scope>test</scope>
</dependency>
当我部署到本地码头服务器时,我的代码运行良好。但是如果我想在我的 IDE 中测试代码,它无法注入 servlet 上下文 (@Context
):当我运行测试时,myContext
是 null
:/
我想我遗漏了一些东西,但我是 servlet 世界的初学者。
【问题讨论】:
【参考方案1】:这是一种使用 Jersey 测试框架和 servlet 支持测试特定资源类的技术。还演示了如何自定义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();
一个用法示例:
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++;
【讨论】:
【参考方案2】:假设您使用的是默认/标准的 Grizzy2 测试框架提供程序,则有一个不需要 spring 的解决方案。根据this answer,jersey-test-framework-provider-grizzly2
框架提供者在构建应用程序上下文时不使用 servlet 环境。你的症状是因为没有ServletContext
实例可以注入。
解决方法是自己为单元测试提供测试容器。首先,修改你的依赖:
<!--<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>
然后,修改您的测试以提供一个 Grizzy servlet 容器:
@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();
;
我假设您将在多个单元测试中使用它,因此扩展JerseyTest
可能是明智之举,以便可以自动执行此通用配置。此外,可能值得查看org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
以查看测试容器是否提供了您希望模拟/保留的任何功能。提供的示例应该能够放入您的测试中,至少可以确认这是一个修复。
编辑:在我自己的实现中,我需要在生成服务器时仍然提供ResourceConfig
的能力。我怀疑这可能是其他 Jersey Test Framework 用户的常见情况。下面是提议的TestContainerFactory
的工作示例。
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);
令人沮丧的是,grizzly 的 GrizzlyWebContainerFactory
将提供一个 servlet 上下文,但不会使用资源配置进行配置。相反,GrizzlyHttpServerFactory
将使用ResourceConfig
配置应用程序,但不会提供 Web 上下文。
我们可以通过手动创建WebappContext
(扩展ServletContext
),对其进行配置,然后通过AbstractBinder
将其注入资源配置来解决此问题。
【讨论】:
【参考方案3】:有几种方法可以做到这一点。删除构造函数并实现一个 configure() 方法,如下所示:
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();
或者您也可以使用 spring 上下文注释您的测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyProcessorTest extends JerseyTest
public static myProcessor mp;
【讨论】:
以上是关于使用 jersey 测试框架时 Servlet 上下文注入失败的主要内容,如果未能解决你的问题,请参考以下文章
Dropwizard / Jersey HTTP Servlet连接重置但泽西资源很好
实例化 servlet 类 org.glassfish.jersey.servlet.ServletContainer 时出错
Jersey - servlet 上下文路径和/或 servlet 路径包含百分比编码的字符
org.glassfish.jersey.servlet.ServletContainer ClassNotFoundException