如何在 JUnit 测试中管理 JNDI 上下文?
How to Manage JNDI Context In JUnit Tests?
我目前正在为使用 guice 进行依赖注入的 java EE 项目编写一些测试。处理实体管理器及其数据源一直给我带来无穷无尽的麻烦。我尝试了多种不同的方法,但它们似乎都在某处存在致命缺陷。我似乎无法弄清楚如何在不交叉污染我的测试的情况下使用 InitialContext。
我的第一次尝试是在我的数据源的 guice 提供程序方法中手动将数据源绑定到上下文中。类似于:
private static Context context;
@Provides
@Singleton
public DataSource getNavDS() throws NamingException {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
if( context == null)
{
context = new InitialContext();
context.createSubcontext("java:");
context.createSubcontext("java:comp");
context.createSubcontext("java:comp/env");
context.createSubcontext("java:comp/env/jdbc");
}
context.rebind(NAV_DS, ds);
return ds;
}
它有点脏,但只要我在每次测试之间重置数据源,它就可以用于单元测试。问题是,我还需要使用嵌入式码头服务器进行集成测试。集成测试有自己的 guice 模块,但如果我调用 new InitialContext()
它最终会给我在之前的测试中创建的实例。这意味着我无法创建测试套件,因为我的单元测试在集成测试之前已经污染了 Context 运行.
我的下一次尝试是尝试使用工厂来模拟上下文查找,如下所述:How to fake InitialContext with default constructor。所以像:
public class TestContextFactory implements InitialContextFactory {
private static final String NAV_DS = "java:comp/env/jdbc/NavDS";
@Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
final BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
Context context = Mockito.mock(Context.class);
Mockito.when(context.lookup(NAV_DS)).thenReturn(ds);
return context;
}
}
问题是,其他代码(如实体管理器)也在对我的模拟上下文进行调用。我最终不得不模拟 InitialContext 中的每个方法以避免异常。
我还尝试在我的提供者方法中不使用上下文。我实际上并没有在我的测试中进行任何手动查找,所以我认为我应该能够初始化和 return 数据源。那也是一个失败。实体管理器期望绑定数据源。如果我不将我的数据源绑定到上下文中,实体管理器将在初始化期间抛出异常。
在这一点上我没有想法。如果有人有更好的方法来处理这个问题(除了简单地嘲笑一切),我很想听听。谢谢
在研究这个问题的另一个想法的过程中,我终于在这里找到了部分解决方案:Junit Testing JNDI InitialContext outside the application server
关键见解是能够 'reset' 测试之间的上下文。一旦我完成了这项工作,我就能够在测试之间清理我的上下文。这样做消除了模拟 Context 的需要;我可以使用真实的实现,并且对它在每次测试开始时所处的状态充满信心。
测试模块的最终版本如下所示:
private static Context context;
private static final String NAV_DS = "java:comp/env/jdbc/NavDS";
//using a provider method here so we can inject the context into the test
@Provides
public Context getContext() throws NamingException
{
if( context == null)
{
context = new InitialContext();
context.createSubcontext("java:comp/env");
context.createSubcontext("java:comp/env/jdbc");
}
return context;
}
@Provides
@Singleton
public DataSource getNavDS() throws NamingException {
BasicDataSource ds = null;
ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
Context context = getContext();
context.rebind(NAV_DS, ds);
return ds;
}
上下文工厂更改为:
public class TestContextFactory implements InitialContextFactory {
private static final ThreadLocal<Context> currentContext = new ThreadLocal<Context>();
@Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
return currentContext.get();
}
public static void setCurrentContext(Context context) {
currentContext.set(context);
}
public static void clearCurrentContext() {
currentContext.remove();
}
}
请注意,由于我使用的是不同的 jndi 实现(jetty 而不是 tomcat),我必须修改上下文设置以匹配。
完成后,我只需要测试中的代码来实际初始化和重置上下文。我按照示例使用了 JUnit 规则,但您可以使用 @Before 和 @After 方法完成同样的事情。规则最终看起来像这样:
public class MockInitialContextRule implements TestRule {
private final Context context;
public MockInitialContextRule(Context context) {
this.context = context;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, TestContextFactory.class.getName());
TestContextFactory.setCurrentContext(context);
try {
base.evaluate();
} finally {
System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
TestContextFactory.clearCurrentContext();
}
}
};
}
}
然后在测试中我注入上下文并初始化规则:
@Inject
private Context context;
@Rule
public MockInitialContextRule mockInitialContextRule = new MockInitialContextRule(context);
@Test
public void testFoo()
{
Object foo = context.lookup("jdbc/NavDS");
assertNotNull(foo);
}
由于我已经发布了赏金,所以我暂时将其打开。如果有人有更好的解决方案,或者对我最终的结果进行改进,请随时留下答案。
我目前正在为使用 guice 进行依赖注入的 java EE 项目编写一些测试。处理实体管理器及其数据源一直给我带来无穷无尽的麻烦。我尝试了多种不同的方法,但它们似乎都在某处存在致命缺陷。我似乎无法弄清楚如何在不交叉污染我的测试的情况下使用 InitialContext。
我的第一次尝试是在我的数据源的 guice 提供程序方法中手动将数据源绑定到上下文中。类似于:
private static Context context;
@Provides
@Singleton
public DataSource getNavDS() throws NamingException {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
if( context == null)
{
context = new InitialContext();
context.createSubcontext("java:");
context.createSubcontext("java:comp");
context.createSubcontext("java:comp/env");
context.createSubcontext("java:comp/env/jdbc");
}
context.rebind(NAV_DS, ds);
return ds;
}
它有点脏,但只要我在每次测试之间重置数据源,它就可以用于单元测试。问题是,我还需要使用嵌入式码头服务器进行集成测试。集成测试有自己的 guice 模块,但如果我调用 new InitialContext()
它最终会给我在之前的测试中创建的实例。这意味着我无法创建测试套件,因为我的单元测试在集成测试之前已经污染了 Context 运行.
我的下一次尝试是尝试使用工厂来模拟上下文查找,如下所述:How to fake InitialContext with default constructor。所以像:
public class TestContextFactory implements InitialContextFactory {
private static final String NAV_DS = "java:comp/env/jdbc/NavDS";
@Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
final BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
Context context = Mockito.mock(Context.class);
Mockito.when(context.lookup(NAV_DS)).thenReturn(ds);
return context;
}
}
问题是,其他代码(如实体管理器)也在对我的模拟上下文进行调用。我最终不得不模拟 InitialContext 中的每个方法以避免异常。
我还尝试在我的提供者方法中不使用上下文。我实际上并没有在我的测试中进行任何手动查找,所以我认为我应该能够初始化和 return 数据源。那也是一个失败。实体管理器期望绑定数据源。如果我不将我的数据源绑定到上下文中,实体管理器将在初始化期间抛出异常。
在这一点上我没有想法。如果有人有更好的方法来处理这个问题(除了简单地嘲笑一切),我很想听听。谢谢
在研究这个问题的另一个想法的过程中,我终于在这里找到了部分解决方案:Junit Testing JNDI InitialContext outside the application server
关键见解是能够 'reset' 测试之间的上下文。一旦我完成了这项工作,我就能够在测试之间清理我的上下文。这样做消除了模拟 Context 的需要;我可以使用真实的实现,并且对它在每次测试开始时所处的状态充满信心。
测试模块的最终版本如下所示:
private static Context context;
private static final String NAV_DS = "java:comp/env/jdbc/NavDS";
//using a provider method here so we can inject the context into the test
@Provides
public Context getContext() throws NamingException
{
if( context == null)
{
context = new InitialContext();
context.createSubcontext("java:comp/env");
context.createSubcontext("java:comp/env/jdbc");
}
return context;
}
@Provides
@Singleton
public DataSource getNavDS() throws NamingException {
BasicDataSource ds = null;
ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
Context context = getContext();
context.rebind(NAV_DS, ds);
return ds;
}
上下文工厂更改为:
public class TestContextFactory implements InitialContextFactory {
private static final ThreadLocal<Context> currentContext = new ThreadLocal<Context>();
@Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
return currentContext.get();
}
public static void setCurrentContext(Context context) {
currentContext.set(context);
}
public static void clearCurrentContext() {
currentContext.remove();
}
}
请注意,由于我使用的是不同的 jndi 实现(jetty 而不是 tomcat),我必须修改上下文设置以匹配。
完成后,我只需要测试中的代码来实际初始化和重置上下文。我按照示例使用了 JUnit 规则,但您可以使用 @Before 和 @After 方法完成同样的事情。规则最终看起来像这样:
public class MockInitialContextRule implements TestRule {
private final Context context;
public MockInitialContextRule(Context context) {
this.context = context;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, TestContextFactory.class.getName());
TestContextFactory.setCurrentContext(context);
try {
base.evaluate();
} finally {
System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
TestContextFactory.clearCurrentContext();
}
}
};
}
}
然后在测试中我注入上下文并初始化规则:
@Inject
private Context context;
@Rule
public MockInitialContextRule mockInitialContextRule = new MockInitialContextRule(context);
@Test
public void testFoo()
{
Object foo = context.lookup("jdbc/NavDS");
assertNotNull(foo);
}
由于我已经发布了赏金,所以我暂时将其打开。如果有人有更好的解决方案,或者对我最终的结果进行改进,请随时留下答案。