如何在unit testing中模拟InitialContext构造函数

当我尝试为Junit测试模拟以下方法(Method使用远程EJB调用业务逻辑)时,它给出了javax.naming.NoInitialContextException

private void someMethod(int id1, int id2, HashMap map){ ......some code........ Context ctx = new InitialContext(); Object ref = ctx.lookup("com.java.ejbs.MyEJB"); EJBHome ejbHome = (EJBHome)PortableRemoteObject.narrow(ref, EJBHome.class); EJBBean ejbBean = (EJBBean)PortableRemoteObject.narrow(ejbHome.create(), EJBBean.class); ejbBean.someMethod(id1,name); .......some code.......} 

我对上述方法的unit testing

 @Test public void testsomeMethod() throws Exception { .......setting initial code... //Mock context and JNDI InitialContext cntxMock = PowerMock.createMock(InitialContext.class); PowerMock.expectNew(InitialContext.class).andReturn(cntxMock); expect(cntxMock.lookup("com.java.ejbs.MyEJB")).andReturn(refMock); ..........some code.......... PowerMock.replayAll(); Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map); } 

Whitebox.invokeMethod(ObjectOfsomeMethodClass,“someMethod”,id1,id2,map)方法调用它时,会给出以下exception。

 javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:645) at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288) at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:325) at javax.naming.InitialContext.lookup(InitialContext.java:392) 

我相信,虽然我们在测试方法中模拟了Context ,但在调用Whitebox.invokeMethod(ObjectOfsomeMethodClass,“someMethod”,id1,id2,map)方法时它并没有使用mock对象,而是试图调用Context ctx = new InitialContext(); 原始方法中的方法(someMethod)。

手工制造

正如InitialContext doc所说,您可以使用java.naming.factory.initial系统属性为InitialContext对象提供自己的工厂。 当代码在应用程序服务器内运行时,系统属性由服务器设置。 在我们的测试中,我们提供了自己的JNDI实现。

这是我的Mockito唯一解决方案:我定义了一个自定义的InitialContextFactory类,它返回一个InitialContext的模拟。 您可以根据需要自定义模拟,可能会在lookup调用上返回更多模拟。

 public class PlainTest { @Mock InitialContextFactory ctx; @InjectMocks Klasa1 klasa1; public static class MyContextFactory implements InitialContextFactory { @Override public Context getInitialContext(Hashtable environment) throws NamingException { ConnectionFactory mockConnFact = mock(ConnectionFactory.class); InitialContext mockCtx = mock(InitialContext.class); when(mockCtx.lookup("jms1")).thenReturn(mockConnFact); return mockCtx; } } @Before public void setupClass() throws IOException { MockitoAnnotations.initMocks(this); System.setProperty("java.naming.factory.initial", this.getClass().getCanonicalName() + "$MyContextFactory"); } 

spring编辑添加)

如果您不介意将Spring Framework用于测试目的,那么这是他们的简单解决方案: SimpleNamingContextBuilder :

 SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); DataSource ds = new DriverManagerDataSource(...); builder.bind("java:comp/env/jdbc/myds", ds); builder.activate(); 

可以将它放在@Before@BeforeClass 。 在activate() ,jndi数据将从spring dummy中拉出。

您可以重构代码并在新方法中提取上下文的初始化。

 private void someMethod(int id1, int id2, HashMap map){ ......some code........ Context ctx = getInitialContext(); Object ref = ctx.lookup("com.java.ejbs.MyEJB"); EJBHome ejbHome = (EJBHome)PortableRemoteObject.narrow(ref, EJBHome.class); EJBBean ejbBean = (EJBBean)PortableRemoteObject.narrow(ejbHome.create(), EJBBean.class); ejbBean.someMethod(id1,name); .......some code.......} 

您的测试代码将是这样的:

 Context mockContext = mock(Context.class); doReturn(mockContext).when(yourclass).getInitalContext(); ...... some code.... 

截至目前(PowerMock 1.7.4)

使用PowerMockito.mock(InitialContext.class)而不是PowerMockito.createMock(InitialContext.class)创建模拟

 @Test public void connectTest() { String jndi = "jndi"; InitialContext initialContextMock = PowerMockito.mock(InitialContext.class); ConnectionFactory connectionFactoryMock = PowerMockito.mock(ConnectionFactory.class); PowerMockito.whenNew(InitialContext.class).withNoArguments().thenReturn(initialContextMock); when(initialContextMock.lookup(jndi)).thenReturn(connectionFactoryMock); ... // Your asserts go here ... } 

不要手动创建InitialContext,而是让PowerMock为您完成 。 另外,不要创建PowerMock期望对象的间谍。 这意味着您需要创建InitialContext实例。

添加到Jarekczek的答案(谢谢!!)。 虽然这是一个古老的问题,但我想分享我的版本,以防它有助于某人。 我遇到了同样的问题,可能只想在工厂中模拟IntialContext,并且可能希望使用其余的对象来模拟其他地方,以便其他文件可以使用它。

 public class MyContextFactory implements InitialContextFactory { //Poor Singleton approach. Not thread-safe (But hope you get the idea) private static InitialContext mockInitialContext; @Override public Context getInitialContext(Hashtable hshtbl) throws NamingException { if(mockInitialContext == null) { mockInitialContext = mock(InitialContext.class); } return mockInitialContext; } } public class TestClass { private DataSource mockDataSource; private Connection mockConnection; protected void mockInitialContext() throws NamingException, SQLException { System.setProperty("java.naming.factory.initial", "com.wrapper.MyContextFactory"); InitialContext mockInitialContext = (InitialContext) NamingManager.getInitialContext(System.getProperties()); mockDataSource = mock(DataSource.class); mockConnection = mock(Connection.class); when(mockInitialContext.lookup(anyString())).thenReturn(mockDataSource); when(mockDataSource.getConnection()).thenReturn(mockConnection); try { when(mockDataSource.getConnection()).thenReturn(mockConnection); } catch (SQLException ex) { Logger.getLogger(CLASSNAME).log(Level.SEVERE, null, ex); } } 

采用这种方法的原因是如果有人想以不同的方式使用数据源或JNDI提供的任何其他资源进行不同的测试,您可以遵循这种方法。 只有一个为IntialContext创建的实例,除非multithreading测试试图同时访问(不知道为什么会尝试这样做!)。 可以在所有位置使用该实例来获取所需的JNDI对象并根据需要使用它们。

希望这可以帮助!

“确保你在每餐前洗手,并在调试健康生活方式时避免使用System.out.println”