如何在单元测试中模拟InitialContext构造函数

时间:2016-04-20 05:05:45

标签: java unit-testing junit ejb

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

我对上述方法的单元测试

@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)

我相信,虽然我们在测试方法中模拟上下文,但在调用 Whitebox.invokeMethod(ObjectOfsomeMethodClass," someMethod",id1)时它不会使用模拟对象,id2,map)方法,而不是试图在原始方法(someMethod)中调用 Context ctx = new InitialContext(); 方法。

5 个答案:

答案 0 :(得分:7)

<强>手工

InitialContext doc所述,您可以使用InitialContext系统属性为java.naming.factory.initial个对象提供自己的工厂。当代码在应用程序服务器内运行时,系统属性由服务器设置。在我们的测试中,我们提供了自己的 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 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中拉出。

答案 1 :(得分:1)

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

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....

答案 2 :(得分:1)

截至目前(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实例。

答案 3 :(得分:0)

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

val intent = Intent(activity, CitationFilterActivity::class.java)
intent.putExtra(CITATION_REQUEST, citationCodeViewModel?.getRequest())
citationCategoryList?.let {
      intent.putExtra(CITATION_FILTER, Gson().toJson(citationCategoryList))
}
//here start activity is used as a result is required as a filter
(activity as HomeActivity).startActivityForResult(intent, CITATION_RESULT)

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

希望这会有所帮助!

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

答案 4 :(得分:0)

定义以下自定义类

public static class CustomInitialContext extends InitialContext {

    Hashtable<String, Object> ic = new Hashtable<>();

    public CustomInitialContext() throws NamingException {
        super(true);
    }

    public void bind(String name, Object object) {
        ic.put(name, object);
    }

    public Object lookup(String name) throws NamingException {
        return ic.get(name);
    }
}

public static class CustomInitialContextFactory implements InitialContextFactory {
    static InitialContext ic;

    public CustomInitialContextFactory() {
        if (ic == null) {
            try {
                ic = new CustomInitialContext();
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
    }

    public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
        return ic;
    }
}

public static class CustomInitialContextFactoryBuilder implements InitialContextFactoryBuilder {

    @Override
    public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
        return new CustomInitialContextFactory();
    }

}

并声明工厂为

NamingManager.setInitialContextFactoryBuilder(new CustomInitialContextFactoryBuilder());