如何使用默认构造函数伪造InitialContext

所有,

我试图在一些古老的java代码中进行一些unit testing(没有接口,没有抽象等)

这是一个使用ServletContext的servlet(我假设它是由Tomcat设置的),并且它在web.xml / context.xml文件中设置了数据库信息。 现在,我已经弄清楚如何制作一个假的ServletContext,但代码有

InitialContext _ic = new InitialContext(); 

到处都是(所以更换它是不可行的)。 我需要找到一种方法来使默认的InitialContext()能够执行_ic.lookup(val)而不抛出exception。

我假设有一些方法可以加载context.xml,但是这种魔法是如何工作的,我正在画一个空白。 有人有主意吗?

您可以使用PowerMock模拟InitialContext的构造并控制其行为。 构造函数Mocking 在此处记录 。

PowerMock测试可能非常混乱和复杂,重构通常是更好的选择。

利用InitialContext使用SPI来处理其创建的事实。 您可以通过创建javax.naming.spi.InitialContextFactory的实现并通过系统属性javax.naming.factory.initialContext.INTITIAL_CONTEXT_FACTORY )将其传递给您的测试来挂钩其生命周期。 它比听起来更简单。

鉴于此课程:

 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 impl:

 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嘲笑和抄袭 。 如果您处理大量遗留代码,我还建议Micheal Feather 有效地使用遗留代码。 这一切都是关于如何在程序中找到接缝以隔离特定的测试件。

这是我为unit testing设置Inintial Context的解决方案。 首先,我将以下测试依赖项添加到我的项目中:

  org.apache.tomcat catalina 6.0.33 test  

然后我用以下代码创建了一个静态方法:

 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); } 

最后,在我的每个测试类中,我添加了一个调用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)并以这种方式解决了这个问题:

  1. 不要在构造函数中查找,因此当您在对象上调用@InitMock时,构造函数不需要上下文。

  2. 创建一个在需要时检索服务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"); } 
  1. 在测试中,设置它:
 @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