How to mock InitialContext constructor in unit tes

2020-02-01 01:42发布

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

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.

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 Context in 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).

4条回答
Melony?
2楼-- · 2020-02-01 02:20

Handmade

As InitialContext doc says, you can provide your own factory for InitialContext objects, using java.naming.factory.initial system 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.

Here's my Mockito only solution: I defined a custom InitialContextFactory class, that returns a mock of InitialContext. You customize the mock as you wish, probably to return more mocks on lookup calls.

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:

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 @Before or @BeforeClass. After activate(), jndi data will be pulled from spring dummy.

查看更多
▲ chillily
3楼-- · 2020-02-01 02:25

Adding to Jarekczek's answer (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.

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.

Hope this helps!

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

查看更多
走好不送
4楼-- · 2020-02-01 02:40

As of now (PowerMock 1.7.4)

Create a mock using PowerMockito.mock(InitialContext.class) rather than 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.

查看更多
女痞
5楼-- · 2020-02-01 02:41

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....
查看更多
登录 后发表回答