[英]How to fake InitialContext with default constructor
全部,
我正在尝试在一些陈旧的 java 代码中进行一些单元测试(没有接口,没有抽象等)
这是一个使用 ServletContext 的 servlet(我假设它是由 Tomcat 设置的)并且它在 web.xml/context.xml 文件中设置了数据库信息。 现在,我已经想出了如何制作一个 Fake ServletContext,但是代码有
InitialContext _ic = new InitialContext();
到处都是(所以更换它是不可行的)。 我需要找到一种方法来使默认的 InitialContext() 能够执行_ic.lookup(val)
而不会引发异常。
我假设有某种方式可以加载 context.xml,但是这个魔法是如何工作的,我正在画一个空白。 谁有想法?
利用InitialContext
使用 SPI 来处理其创建这一事实。 您可以通过创建javax.naming.spi.InitialContextFactory
的实现并通过系统属性javax.naming.factory.initial
( Context.INTITIAL_CONTEXT_FACTORY
) 将其传递给您的测试来挂钩其生命周期。 它比听起来简单。
鉴于此 class:
public class UseInitialContext {
public UseInitialContext() {
try {
InitialContext ic = new InitialContext();
Object myObject = ic.lookup("myObject");
System.out.println(myObject);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
而InitialContextFactory
的这个含义:
public class MyInitialContextFactory implements InitialContextFactory {
public Context getInitialContext(Hashtable<?, ?> arg0)
throws NamingException {
Context context = Mockito.mock(Context.class);
Mockito.when(context.lookup("myObject")).thenReturn("This is my object!!");
return context;
}
}
在 junit 测试中创建UseInitialContext
实例
-Djava.naming.initial.factory=initial.context.test.MyInitialContext
在命令行输出This is my object!!
(很容易在日食中设置)。 我喜欢Mockito用于 mocking 和存根。 如果您处理大量遗留代码,我还推荐 Micheal Feather 的Working Effectively with Legacy Code 。 这一切都是关于如何在程序中找到接缝,以便隔离特定的部分进行测试。
这是我为单元测试设置 Inintial Context 的解决方案。 首先,我将以下测试依赖项添加到我的项目中:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
<version>6.0.33</version>
<scope>test</scope>
</dependency>
然后我用下面的代码创建了一个 static 方法:
public static void setupInitialContext() throws Exception {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ic = new InitialContext();
ic.createSubcontext("jdbc");
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setDatabaseName("postgres");
ds.setUser("postgres");
ds.setPassword("admin");
ic.bind("jdbc/something", ds);
}
最后,在我的每个测试 class 中,我添加了一个调用 setupInitialContext 的 @BeforeClass 方法。
之前尝试设置系统变量:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,
"org.apache.naming");
InitialContext ic = new InitialContext();
如果您使用的是 JUnit,请遵循此文档: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit
今天我遇到了同样的问题(我们不能使用 PowerMock)并以这种方式解决了它:
不要在构造函数中查找,因此当您在 object 上调用 @InitMock 时,构造函数还不需要上下文。
创建一个在需要时检索服务 bean 的方法,如“getService().serviceMethod(param, param...)”:
/* Class ApplicationResourceProvider */ /* We can mock this and set it up with InjectMocks */ InitialContext ic; /* method hiding the lookup */ protected ApplicationService getService() throws NamingException { if(ic == null) ic = new InitialContext(); return (ApplicationService)ic.lookup("java:global/defaultApplicationLocal"); }
@Mock ApplicationService applicationServiceBean; @Mock InitialContext ic; @InjectMocks ApplicationResourceProvider arp; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(ic.lookup(anyString())).thenReturn(applicationServiceBean); ... }
你考虑过 mockito吗?
很简单:
InitialContext ctx = mock(InitialContext.class);
顺便说一句,如果您选择使用模拟,我也建议您阅读这篇文章: http://martinfowler.com/articles/mocksArentStubs.html
不使用外部库的穷人独立实现:
public class myTestClass {
public static class TestContext extends InitialContext {
public TestContext() throws NamingException {
super(true /*prevents initialization*/);
}
static Object someExpectedValue = "the expected string or object instance";
/*override the method(s) called by the legacy program on _ic, check the parameter and return the wanted value */
public Object lookup(String name) throws NamingException {
return name != null && name.equals("theValueOfVal") ? someExpectedValue : null;
}
}
public static class TestInitialContextFactory implements InitialContextFactory {
public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
return new TestContext();
}
}
public static void main(String[] args) throws SQLException {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "the.package.myTestClass$TestInitialContextFactory");
/*now call the legacy logic to be tested*/
...
您可以在lookup
方法的覆盖中使用一个switch
,以返回整个遗留程序中传递给_ic.lookup(val)
的每个不同val
值的预期值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.