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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36734275/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 01:49:22  来源:igfitidea点击:

How to mock InitialContext constructor in unit testing

javaunit-testingjunitejb

提问by Anushka Senarathna

when I try to mock following method(Method is using remote EJB call for business logic) for the Junit test, it gives javax.naming.NoInitialContextException

当我尝试为 Junit 测试模拟以下方法(方法使用远程 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.......}

My unit test for above method

我对上述方法的单元测试

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


}

when the Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map)method invokes it gives following exception.

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)

I believe, although we mock the Contextin test method, it does not use the mock object when calling Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map)method, instead of that its trying to invoke the Context ctx = new InitialContext();method in original method(someMethod).

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

回答by Jarekczek

Handmade

手工制作的

As InitialContextdocsays, you can provide your own factory for InitialContextobjects, using java.naming.factory.initialsystem property. When the code runs inside application server, the system property is set by the server. In our tests, we provide our own implementation of JNDI.

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

Here's my Mockitoonly solution: I defined a custom InitialContextFactoryclass, that returns a mock of InitialContext. You customize the mock as you wish, probably to return more mocks on lookupcalls.

这是我的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(added by edit)

弹簧(由编辑添加)

If you don't mind leveraging Spring Framework for testing purposes, here's their simple solution: SimpleNamingContextBuilder:

如果您不介意利用 Spring Framework 进行测试,这里是他们的简单解决方案:SimpleNamingContextBuilder

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

It's ok to put it in @Beforeor @BeforeClass. After activate(), jndi data will be pulled from spring dummy.

放在@Beforeor 里就可以了@BeforeClass。之后activate(),jndi 数据将从 spring dummy 中拉出。

回答by S.Stavreva

You can refactor your code and extract the initialization of the context in new method.

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

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

Your test code will be something like this:

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

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

回答by Ilker Cat

As of now (PowerMock 1.7.4)

截至目前(PowerMock 1.7.4)

Create a mock using PowerMockito.mock(InitialContext.class)rather than PowerMockito.createMock(InitialContext.class)

使用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 ...
}

Do not create the InitialContext manually but let PowerMock do it for you. Also do not create a spy in which PowerMock expects an object. This means that you need to create the InitialContext instance.

不要手动创建 InitialContext,而是让 PowerMock 为您创建。也不要创建一个 PowerMock 需要一个对象的间谍。这意味着您需要创建 InitialContext 实例。

回答by Kaustav Sarkar

Adding to Jarekczek'sanswer (Thanks for it!!). Though it is an old question I would like to share my version of it in case it helps someone. I faced the same problem and one might just want to mock IntialContext only in a IntialContextFactory implementation class and it would be a better idea to use this mocked object in other tests or base test classes to avoid duplication.

添加到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);
        }
    }
}

Reason behind taking this approach being if someone wants to use DataSource or any other resource provided by JNDI in a different way for different tests, you can follow this approach. There shall be just one instance created for IntialContext unless a multi-threaded test tries to access it simultaneously (don't know why would one try to do that!). That instance can be used in all places to get JNDI objects you want and use them as you want.

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

Hope this helps!

希望这可以帮助!

"Make sure you wash your hands before every meal and avoid System.out.println while debugging for healthy lifestyle"

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