How to fake InitialContext with default constructor
Asked Answered
B

7

14

All,

I'm trying to do some unit testing in some archaic java code (no interfaces, no abstraction, etc.)

This is a servlet that uses a ServletContext (which I'm assuming is set up by Tomcat) and it has database information is set up in the web.xml/context.xml file. Now, I've figured out how to make a Fake ServletContext, but the code has

 InitialContext _ic = new InitialContext();

all over the place (so it isn't feasible to replace it). I need to find a way to make a default InitialContext() able to do the _ic.lookup(val) without throwing an exception.

I'm assuming there is some way that the context.xml is getting loaded, but how that magic works, I'm drawing a blank. Anyone have any ideas?

Bode answered 6/4, 2012 at 15:17 Comment(1)
Just because it occurs a lot doesn't mean it's definitely infeasible to replace it. Heck, even just changing to use a static factory method would allow more testability (although it's clearly not as nice as some alternatives).Transpacific
F
3

You can use PowerMock to mock construction of the InitialContext and control its behavior. Constructor Mocking is documented here.

PowerMock tests can be quite messy and complicated, refactoring is normally a better option.

Flabbergast answered 6/4, 2012 at 15:33 Comment(3)
+1. PowerMock is powerful but it's definitely caused enough headaches that refactoring is much preferred.Angrist
I think this is the best solution for what i'm trying to do. thanks!Bode
Avoid PowerMock unless you have no other option left open to you... Plenty of articles explaining why.Otology
W
37

Take advantage of the fact that InitialContext uses an SPI to handle its creation. You can hook into its lifecycle by creating an implementation of javax.naming.spi.InitialContextFactory and passing that to your tests via the system property javax.naming.factory.initial (Context.INTITIAL_CONTEXT_FACTORY). It's simpler than it sounds.

Given this class:

public class UseInitialContext {

    public UseInitialContext() {
        try {
            InitialContext ic = new InitialContext();
            Object myObject = ic.lookup("myObject");
            System.out.println(myObject);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }


} 

And this impl of InitialContextFactory:

public class MyInitialContextFactory implements InitialContextFactory {

    public Context getInitialContext(Hashtable<?, ?> arg0)
            throws NamingException {

        Context context = Mockito.mock(Context.class);
        Mockito.when(context.lookup("myObject")).thenReturn("This is my object!!");
        return context;
    }
}

Creating an instance of UseInitialContext in a junit test with

-Djava.naming.initial.factory=initial.context.test.MyInitialContext

on the command line outputs This is my object!! (easy to set up in eclipse). I like Mockito for mocking and stubbing. I'd also recommend Micheal Feather's Working Effectively with Legacy Code if you deal with lots of legacy code. It's all about how to find seams in programs in order to isolate specific pieces for testing.

Whittier answered 6/4, 2012 at 18:10 Comment(4)
Instead of 'java.naming.initial.factory', I think the correct system property is 'java.naming.factory.initial'. At least in java 6. Thanks for the post!Kneedeep
'java.naming.factory.initial' is also for java 7.Dormouse
FWIW, i wrote a very similar answer to a very similar question some time after this, and i did this using a JUnit TestTule to handle the setup and teardown.Seedling
Context.INITIAL_CONTEXT_FACTORY so you don't want to remember the actual string value for the initial context factory environment variable name.Waneta
H
6

Here's my solution to setting up the Inintial Context for my unit tests. First I added the following test dependency to my project:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>catalina</artifactId>
  <version>6.0.33</version>
  <scope>test</scope>
</dependency>

Then I created a static method with the following code:

public static void setupInitialContext() throws Exception {
    System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
    System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
    InitialContext ic = new InitialContext();
    ic.createSubcontext("jdbc");
    PGSimpleDataSource ds = new PGSimpleDataSource();
    ds.setDatabaseName("postgres");
    ds.setUser("postgres");
    ds.setPassword("admin");
    ic.bind("jdbc/something", ds);
}

Finally in each of my test class I add an @BeforeClass method which calls setupInitialContext.

Harlie answered 6/4, 2012 at 16:14 Comment(1)
in relation to this blogs.oracle.com/randystuph/entry/… was very goodWilderness
S
4

Try setting up the system variables before:

System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,
        "org.apache.naming");
InitialContext ic = new InitialContext();

If you are using JUnit, follow this doc: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit

Sonnysonobuoy answered 6/4, 2012 at 16:4 Comment(4)
This gives java.lang.ClassNotFoundException: org.apache.naming.java.javaURLContextFactoryComical
you are supposed to have the tomcat environment in your project. You have to add the tomcat jarsSonnysonobuoy
So if I am using JBoss I guess I have to use JBoss environment. Thank you!Comical
The link to oracle blog is brokenLymphoma
F
3

You can use PowerMock to mock construction of the InitialContext and control its behavior. Constructor Mocking is documented here.

PowerMock tests can be quite messy and complicated, refactoring is normally a better option.

Flabbergast answered 6/4, 2012 at 15:33 Comment(3)
+1. PowerMock is powerful but it's definitely caused enough headaches that refactoring is much preferred.Angrist
I think this is the best solution for what i'm trying to do. thanks!Bode
Avoid PowerMock unless you have no other option left open to you... Plenty of articles explaining why.Otology
V
1

Today I've faced the same problem (we can't user PowerMock) and solved it this way:

  1. Don't lookup in the constructor so when you invoke @InitMock on the object, the constructor doesn't require the context yet.

  2. Create a method for retrieving the service bean when needed like "getService().serviceMethod(param, param ...)":

    /* Class ApplicationResourceProvider */

    /* We can mock this and set it up with InjectMocks */
    InitialContext ic;

    /* method hiding the lookup */
    protected ApplicationService getService() throws NamingException {
        if(ic == null)
            ic = new InitialContext();
        return (ApplicationService)ic.lookup("java:global/defaultApplicationLocal");
    }
  1. On the test, set it up:
@Mock
ApplicationService applicationServiceBean;

@Mock
InitialContext ic;

@InjectMocks
ApplicationResourceProvider arp;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    when(ic.lookup(anyString())).thenReturn(applicationServiceBean);
    ...
}
Vestavestal answered 4/11, 2016 at 16:31 Comment(0)
S
0

Have you considered mockito?

It's as easy as:

InitialContext ctx = mock(InitialContext.class);

By the way, should you choose to use mocks i would recommend reading this article as well: http://martinfowler.com/articles/mocksArentStubs.html

Sharyl answered 6/4, 2012 at 16:22 Comment(0)
H
0

A poor man's standalone implementation using no external libraries:

public class myTestClass {
    public static class TestContext extends InitialContext {
        public TestContext() throws NamingException {
            super(true /*prevents initialization*/);
        }

        static Object someExpectedValue = "the expected string or object instance";

        /*override the method(s) called by the legacy program on _ic, check the parameter and return the wanted value */
        public Object lookup(String name) throws NamingException {
            return name != null && name.equals("theValueOfVal") ? someExpectedValue : null;
        }
    }

    public static class TestInitialContextFactory implements InitialContextFactory {
        public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
            return new TestContext();
        }
    }

    public static void main(String[] args) throws SQLException {
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "the.package.myTestClass$TestInitialContextFactory");
        /*now call the legacy logic to be tested*/
        ...

You could use a switch in the override of the lookup method to return the expected value for each different val value passed to _ic.lookup(val) throughout the legacy program.

Hyozo answered 3/10, 2019 at 14:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.