How to mock InitialContext constructor in unit testing
Asked Answered
L

5

14

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

Lycaonia answered 20/4, 2016 at 5:5 Comment(1)
You could add an in-memory InitialContextFactory into your tests, Example: stackoverflow.com/questions/3461310Apochromatic
T
16

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 before version 6 (added by edit)

Another edit: this is deprecated in 5.2 and removed in 6.0.

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.

Thaliathalidomide answered 21/5, 2017 at 11:44 Comment(2)
What is the fully qualified class name of the ConnectionFactory.class that is getting mocked?Waldron
I think in new versions of Spring the class SimpleNamingContextBuilderhas been removed. The link in your answer is dead.Pacheco
S
4

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"

Selfinductance answered 28/9, 2018 at 12:41 Comment(1)
I'm trying to employ this solution for my unit tests too. One thing to get clarified, the value "com.wrapper.MyContextFactory", is it specific for the running server? Or how to determine the correct value for that? In other words, for what value should I lookup the context for?Jardine
C
1

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....
Chkalov answered 20/4, 2016 at 7:15 Comment(1)
S.Stavreva thanks for the your reply, but i am going to write junit test for existing method, therefore i can not change the actual method implementation.Lycaonia
R
1

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.

Rematch answered 8/8, 2018 at 20:57 Comment(0)
F
1

Define the following Custom Classes

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();
    }

}

and declare factory as

NamingManager.setInitialContextFactoryBuilder(new CustomInitialContextFactoryBuilder());
Firecrest answered 21/6, 2021 at 3:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.