Junit Testing JNDI InitialContext outside the application server
Asked Answered
M

4

27
Context context = new InitialContext();
dataSource = (DataSource) context.lookup("java:comp/env/jdbc/multiDS");
connection = dataSource.getConnection();

Please help me to mock the above code.

Hi Tom Anderson

I tried the below code

 @BeforeClass
 public static void setUpClass() throws Exception {
        // rcarver - setup the jndi context and the datasource
        try {
            // Create initial context
            System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
                "org.apache.naming.java.javaURLContextFactory");
            System.setProperty(Context.URL_PKG_PREFIXES, 
                "org.apache.naming");            
            Context ic = new InitialContext();

            ic.createSubcontext("java:");
            ic.createSubcontext("java:comp");
            ic.createSubcontext("java:comp/env");
            ic.createSubcontext("java:comp/env/jdbc");
            ic.createSubcontext("java:comp/env/jdbc/multiDS");
            // Construct DataSource
            OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
            ds.setURL("jdbc:oracle:thin:@g9u0696.houston.hp.com:1525:CRNAD");
            ds.setUser("uname");
            ds.setPassword("pwd");
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
 }

But its giving error as:

com.hp.corona.common.exception.CacheException: org.apache.naming.NamingContext cannot be cast to javax.sql.DataSource

Please help me to test the code i just want connection from JNDI datasource

Marela answered 13/6, 2013 at 9:3 Comment(1)
it really depends on your app server . any ways take a look at this codeproject.com/Articles/25741/…Nathalia
J
29

The orthodox thing to do here would be to change you code so that the Context is injected into it (by a dependency injection framework, or manually). Then, you simply pass in a mock in your unit test.

If you can't do this, and your code must create the IntialContext itself, then you will need to set up a fake JNDI implementation into which you can inject mocks. If you search the web for in-memory JNDI implementation or mock JNDI implementation, you will find various options, or you could write one yourself. Basically, you will need an implementation of InitialContextFactory which simply returns a suitable mock, which you then select by setting the java.naming.factory.initial system property.

I had a crack at writing the necessary classes. Here you go:

public class MockInitialContextFactory implements InitialContextFactory {

    private static final ThreadLocal<Context> currentContext = new ThreadLocal<Context>();

    @Override
    public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
        return currentContext.get();
    }

    public static void setCurrentContext(Context context) {
        currentContext.set(context);
    }

    public static void clearCurrentContext() {
        currentContext.remove();
    }

}

public class MockInitialContextRule implements TestRule {

    private final Context context;

    public MockInitialContextRule(Context context) {
        this.context = context;
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                System.setProperty(Context.INITIAL_CONTEXT_FACTORY, MockInitialContextFactory.class.getName());
                MockInitialContextFactory.setCurrentContext(context);
                try {
                    base.evaluate();
                } finally {
                    System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
                    MockInitialContextFactory.clearCurrentContext();
                }
            }
        };
    }
}

Use as follows:

public class FooTest {

    private final Context context = mock(Context.class);

    @Rule
    public MockInitialContextRule mockInitialContextRule = new MockInitialContextRule(context);

    @Test
    public void testName() throws Exception {
        // set up stubbings on the context mock
        // invoke the code under test
    }
}
Jetton answered 13/6, 2013 at 9:33 Comment(10)
Hi Mr Anderson.. i Just edited the testcode please give me suggestionMarela
Your code isn't right. Compare it to Randy Carver's original code: blogs.oracle.com/randystuph/entry/… . You are doing a createSubcontext when you should be doing a bind. The exception message is hinting at that - you have created a NamingContext where you should have created a DataSource.Jetton
Hi Mr Anderson i dont know about JNDI .. so not getting what you suggesting me please help me to correct the codeMarela
You should do two things. Firstly, learn the basics of JNDI, perhaps by reading the JNDI trail of the Java tutorial. How can you hope to do this work without understanding JNDI? Secondly, as i said, compare your code with the original version of you have copied. There is a difference which you have added which is the cause of the problem.Jetton
Hi Mr Anderson i corrected the testcode by comparing ...its working Thanks a lot.Marela
I think the same question is answered pretty clearly hereJitter
@soltysh: That is indeed basically the same idea. My version has the sweet TestRule, though.Jetton
thank you @TomAnderson this was really very helpful.Rusk
why MockInitialContextRule constructor is private?Sparid
@Sparid I think that's a mistake - i'll make it public. Thanks for catching it!Jetton
L
8

You can use the Spring implementation, it works like this:

import org.springframework.mock.jndi.SimpleNamingContextBuilder;

[...]

SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("jdbc/myDataSource", myDS);
builder.activate();
Libbie answered 13/10, 2015 at 12:10 Comment(0)
D
2

This is easily done with Simple-JNDI. Create a property file "jdbc/multiDS.properties" in your working directory to configure your datasource with these properties:

type=javax.sql.DataSource
driver=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost/testdb
user=testuser
password=testing

Then instantiate the context with

final Hashtable<String, String> env = new Hashtable<String, String>();
env.put("org.osjava.sj.root", "working_dir");
env.put("org.osjava.sj.jndi.shared", "true");
env.put("java.naming.factory.initial", "org.osjava.sj.SimpleContextFactory");
env.put("org.osjava.sj.delimiter", "/");
env.put("org.osjava.sj.space", "java:comp/env")
Context ctx = new InitialContext(env);

After that you can call

dataSource = (DataSource) context.lookup("java:comp/env/jdbc/multiDS");

Find more info about Simple-JNDI here https://github.com/h-thurow/Simple-JNDI

Dendroid answered 4/2, 2017 at 11:24 Comment(1)
If you're using Tomcat as your container and doing all this to try and test some code you normally run in Tomcat, check out TomcatJNDI that Simple-JNDI points to.Coin
T
2

I had problems using the suggested solutions on a legacy system I am adding tests to, but I have found that using Mockito.mockConstruction can be a simple approach:

public class FooTest {
  private final Context mockContext = mock(Context.class);

  @Test
  public void myTest()
      throws NamingException {

    try (final MockedConstruction<InitialContext> ignored = 
               mockConstruction(InitialContext.class,
               (mock, context) -> 
                    when(mock.lookup("java:comp/env"))
                    .thenReturn(mockContext)) {
    // Given
    when(mockContext.lookup("environmentEntry"))
        .thenReturn(desiredResult);
    ...
  }
}
Trompe answered 23/3, 2022 at 15:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.