I have a Singleton/Factory object that I'd like to write a JUnit test for. The Factory method decides which implementing class to instantiate based upon a classname in a properties file on the classpath. If no properties file is found, or the properties file does not contain the classname key, then the class will instantiate a default implementing class.
Since the factory keeps a static instance of the Singleton to use once it has been instantiated, to be able to test the "failover" logic in the Factory method I would need to run each test method in a different classloader.
Is there any way with JUnit (or with another unit testing package) to do this?
edit: here is some of the Factory code that is in use:
private static MyClass myClassImpl = instantiateMyClass(); private static MyClass instantiateMyClass() { MyClass newMyClass = null; String className = null; try { Properties props = getProperties(); className = props.getProperty(PROPERTY_CLASSNAME_KEY); if (className == null) { log.warn("instantiateMyClass: Property [" + PROPERTY_CLASSNAME_KEY + "] not found in properties, using default MyClass class [" + DEFAULT_CLASSNAME + "]"); className = DEFAULT_CLASSNAME; } Class MyClassClass = Class.forName(className); Object MyClassObj = MyClassClass.newInstance(); if (MyClassObj instanceof MyClass) { newMyClass = (MyClass) MyClassObj; } } catch (...) { ... } return newMyClass; } private static Properties getProperties() throws IOException { Properties props = new Properties(); InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_FILENAME); if (stream != null) { props.load(stream); } else { log.error("getProperties: could not load properties file [" + PROPERTIES_FILENAME + "] from classpath, file not found"); } return props; }
A class is always identified using its fully qualified name (package. classname). So when a class is loaded into JVM, you have an entry as (package, classname, classloader). Therefore the same class can be loaded twice by two different ClassLoader instances.
Unit tests can be in any package. In essence they are just separate classes used to test the behaviour of the class being tested.
Types of Built-in Class Loaders As we can see, there are three different class loaders here: application, extension, and bootstrap (displayed as null). The application class loader loads the class where the example method is contained.
This question might be old but since this was the nearest answer I found when I had this problem I though I'd describe my solution.
Using JUnit 4
Split your tests up so that there is one test method per class (this solution only changes classloaders between classes, not between methods as the parent runner gathers all the methods once per class)
Add the @RunWith(SeparateClassloaderTestRunner.class)
annotation to your test classes.
Create the SeparateClassloaderTestRunner
to look like this:
public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner { public SeparateClassloaderTestRunner(Class<?> clazz) throws InitializationError { super(getFromTestClassloader(clazz)); } private static Class<?> getFromTestClassloader(Class<?> clazz) throws InitializationError { try { ClassLoader testClassLoader = new TestClassLoader(); return Class.forName(clazz.getName(), true, testClassLoader); } catch (ClassNotFoundException e) { throw new InitializationError(e); } } public static class TestClassLoader extends URLClassLoader { public TestClassLoader() { super(((URLClassLoader)getSystemClassLoader()).getURLs()); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.startsWith("org.mypackages.")) { return super.findClass(name); } return super.loadClass(name); } } }
Note I had to do this to test code running in a legacy framework which I couldn't change. Given the choice I'd reduce the use of statics and/or put test hooks in to allow the system to be reset. It may not be pretty but it allows me to test an awful lot of code that would be difficult otherwise.
Also this solution breaks anything else that relies on classloading tricks such as Mockito.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With