2

Question
Is it possible to provide an implementation of a class, using a custom ClassLoader, that will be properly utilized from a static context?

Background
I am working with a framework which recommends we use a static class to wire up dependencies.
It works something like this..

public class MyClass {

    @ThisIsADependency
    private MyDependency myDependency;

    public void initialize() {
        FrameworkProvidedDependencyResolver.resolveDependencies(this);
    }

}

As you might expect, this is a nightmare to test with, and, sure enough FrameworkProvidedDependencyResolver (not the real name) throws a NullPointerException unless called from within an active framework environment which is not possible from JUnit.

What I would like to do, is provide a custom ClassLoader which I can use in JUnit tests to provide a custom FrameworkProvidedDependencyResolver which wires up mock dependencies or whatever.

Ok, so here's what I'd like my unit tests to look like:

@RunWith(MyTestRunner.class)
public class TestMyClass {

    @Test
    public void testInitialization() {
        MyClass myClass = new MyClass();
        myClass.initialize();
        // not much of a test, I know
    }

}

MyTestRunner is where I opt to use my custom ClassLoader..

public class MyTestRunner extends BlockJUnit4ClassRunner {

    public MyTestRunner(Class<?> clazz) throws InitializationError {
        super(getFromMyClassLoader(clazz));
    }

    private static Class<?> getFromMyClassLoader(Class<?> clazz) throws InitializationError {
        try {
            ClassLoader testClassLoader = new MyClassLoader();
            return Class.forName(clazz.getName(), true, testClassLoader);
        } catch (ClassNotFoundException e) {
            throw new InitializationError(e);
        }
    }

}

Thanks @AutomatedMike.

Ok, so that slips MyClassLoader into the mix where I can get a chance to swap out FrameworkProvidedDependencyResolver with a custom dependency resolver for testing..

public class ZKTestClassLoader extends URLClassLoader {

    public ZKTestClassLoader() {
        super(((URLClassLoader) getSystemClassLoader()).getURLs());
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) {
            return loadedClass;
        }
        System.out.println("Loading " + name);
        if (name.startsWith("my.test.classes")) {
            // Make sure we use MyClassLoader to load the test classes,
            // thus any classes it loads (eg: MyClass) will come through here.
            return super.findClass(name);
        } else if (name.endsWith("FrameworkProvidedDependencyResolver")) {
            // What should do we do here?
        }
        return super.loadClass(name);
    }

}

Ok, and so now we are in a position to load a custom FrameworkProvidedDependencyResolver instead of the one provided by the framework.. but how do I do that?

I can ignore the request for 'FrameworkProvidedDependencyResolver' and return another class, say, 'MyMockFrameworkProvidedDependencyResolver'. That's fine but when MyClass.initialize calls for FrameworkProvidedDependencyResolver from a static context, we get a NoClassDefFoundError. Makes sense.

I can try naming MyMockFrameworkProvidedDependencyResolver the same as the real FrameworkProvidedDependencyResolver and put it in another package (eg: i.hate.my.framework.FrameworkProvidedDependencyResolver). This also doesn't work as the MyClass is specifically looking at the real FrameworkProvidedDependencyResolver, package and all.

I can try naming my class as the real FrameworkProvidedDependencyResolver and putting it in the same package as that provided by my framework.. but now I don't even need a ClassLoader. the JVM will be confused by the two and load whichever is appropriate by the classpath, likely mine. The problem here is that this now applies to all tests; not the solution I'm looking for.

Lastly, I cannot use Proxy because FrameworkProvidedDependencyResolver is not an interface.

Ok, to restate my question:
Is it possible to provide an implementation of a class, using a custom ClassLoader, that will be properly utilized from a static context? Perhaps, can I have a class in it's own unique path with a unique name, which I can edit as I load it, such that it will appear in the JVM with the expected path and name I'm trying to override? Any other solution is, of course, welcome.

Community
  • 1
  • 1
Sean Connolly
  • 5,692
  • 7
  • 37
  • 74

2 Answers2

1

First, you should question if it's really necessary to mock the static resolveDependencies() method. Instead, you could make initialize() delegate to another object/method and mock that. Or you could use a half-mock (e.g. via a Mockito spy) that mocks the initialize method on the class under test. Or you could make MyClass so small (by moving functionality into other classes) that it no longer needs to be (unit) tested. Or maybe you can prevent initialize() from being called and do your own initialization instead.

If you come to the conclusion that you absolutely need to mock static methods, by all means use a mocking framework that supports this, rather than inventing your own solution (which will be difficult). Two well-known contenders in this market are PowerMock and JMockit.

PS: It's not clear to me why you are deliberately invoking the initialize method from the test. What's the intent?

Peter Niederwieser
  • 121,412
  • 21
  • 324
  • 259
1

I found your question while trying to sort out things with classloaders and your code helped me understand where I was wrong. I then played with your code to achieve what you seem to be asking for. And even though Peter Nederwieser is right about every point and using mocking frameworks (PowerMock, JMockit) would be the way to go, just for the sake of completeness here's my version of loadClass that works for me:

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> loadedClass = findLoadedClass(name);
    if (loadedClass != null) {
        return loadedClass;
    }
    System.out.println("Loading " + name);
    if (name.endsWith("FrameworkProvidedDependencyResolver")) {
        try {
            InputStream is =
                super
                    .getResourceAsStream("FrameworkProvidedDependencyResolver.class");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for (int i = is.read(); i != -1; i = is.read()) {
                baos.write(i);
            }
            byte[] buf = baos.toByteArray();
            return defineClass(name, buf, 0, buf.length);
        } catch (Exception e) {
            throw new ClassNotFoundException("", e);
        }
    } else if (name.startsWith("my.test.classes")) {
        // Make sure we use MyClassLoader to load the test classes,
        // thus any classes it loads (eg: MyClass) will come through here.
        return super.findClass(name);
    }
    return super.loadClass(name);
}

FrameworkProvidedDependencyResolver.class is the compiled modified class. It should have the same package and name as the original FrameworkProvidedDependencyResolver, therefore it can be a bit tricky to make it co-exist in the same project with the original FrameworkProvidedDependencyResolver: at least the IDE won't be happy. I just created a class, edited it, then grabbed its compiled class-file from the IDE build folder, put it to the classes root (that's why I'm doing super.getResourceAsStream("FrameworkProvidedDependencyResolver.class") without any path), and then renamed the java file to something different. In the real environment the way to obtain/store the bytecode would probably be different (and maybe isn't worth the try).

The reason I reordered the two conditional branches is that I had everything in the same package, it's most likely not your case.

starikoff
  • 1,601
  • 19
  • 23