How to mock InitialContext constructor in unit testing

后端 未结 4 466
臣服心动
臣服心动 2020-12-06 03:16

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

p         


        
相关标签:
4条回答
  • 2020-12-06 03:21

    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"

    0 讨论(0)
  • 2020-12-06 03:30

    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....
    
    0 讨论(0)
  • 2020-12-06 03:32

    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.

    0 讨论(0)
  • 2020-12-06 03:33

    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.

    0 讨论(0)
提交回复
热议问题