如何在单元测试中模拟初始上下文构造函数

2022-09-04 08:44:12

当我尝试模拟以下方法(方法使用远程EJB调用业务逻辑)进行Junit测试时,它给出了javax.nameing.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.......}

我对上述方法的单元测试

@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) 方法调用时,它会给出以下异常。

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 对象,而不是尝试在原始方法(someMethod)中调用 Context ctx = new InitialContext(); 方法。


答案 1

手工

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

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

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 Framework进行测试,以下是他们的简单解决方案:SimpleNamingContextBuilder

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

可以将其放入或.之后,jndi数据将从弹簧假人中提取。@Before@BeforeClassactivate()


答案 2

添加到Jarekczek的答案(谢谢你!)。虽然这是一个古老的问题,但我想分享我的版本,以防万一它帮助了某人。我遇到了同样的问题,人们可能只想在IntialContextFactory实现类中模拟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);
        }
    }
}

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

希望这有帮助!

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


推荐