Вопрос
Можно ли предоставить реализацию класса с использованием пользовательского ClassLoader
, который будет правильно использоваться в статическом контексте?
Предыстория
Я работаю с платформой, которая рекомендует использовать статический класс для подключения зависимостей.
Это работает примерно так..
public class MyClass {
@ThisIsADependency
private MyDependency myDependency;
public void initialize() {
FrameworkProvidedDependencyResolver.resolveDependencies(this);
}
}
Как и следовало ожидать, это кошмар для тестирования, и, конечно же, FrameworkProvidedDependencyResolver
(не настоящее имя) выдает NullPointerException
, если только он не вызывается из активной среды фреймворка, что невозможно из JUnit.
Что я хотел бы сделать, так это предоставить пользовательский ClassLoader
, который я могу использовать в тестах JUnit, чтобы предоставить пользовательский FrameworkProvidedDependencyResolver
, который подключает фиктивные зависимости или что-то еще.
Итак, вот как я хотел бы, чтобы мои модульные тесты выглядели:
@RunWith(MyTestRunner.class)
public class TestMyClass {
@Test
public void testInitialization() {
MyClass myClass = new MyClass();
myClass.initialize();
// not much of a test, I know
}
}
MyTestRunner
— это то место, где я предпочитаю использовать свой собственный 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);
}
}
}
Спасибо @AutomatedMike.
Итак, это добавляет MyClassLoader
в смесь, где я могу получить возможность заменить FrameworkProvidedDependencyResolver
на собственный преобразователь зависимостей для тестирования..
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);
}
}
Итак, теперь мы можем загрузить пользовательский FrameworkProvidedDependencyResolver
вместо того, который предоставляется фреймворком... но как мне это сделать?
Я могу проигнорировать запрос «FrameworkProvidedDependencyResolver» и вернуть другой класс, скажем, «MyMockFrameworkProvidedDependencyResolver». Это нормально, но когда MyClass.initialize
вызывает FrameworkProvidedDependencyResolver
из статического контекста, мы получаем NoClassDefFoundError
. Имеет смысл.
Я могу попробовать назвать MyMockFrameworkProvidedDependencyResolver
так же, как настоящий FrameworkProvidedDependencyResolver
, и поместить его в другой пакет (например: i.hate.my.framework.FrameworkProvidedDependencyResolver
). Это также не работает, так как MyClass
специально смотрит на настоящего FrameworkProvidedDependencyResolver
, упаковку и все такое.
Я могу попробовать назвать свой класс настоящим FrameworkProvidedDependencyResolver
и поместить его в тот же пакет, что и мой фреймворк... но теперь мне даже не нужен ClassLoader
. JVM будет сбита с толку этими двумя и загрузит то, что подходит по пути к классам, вероятно, моему. Проблема здесь в том, что теперь это относится ко всем тестам; не то решение, которое я ищу.
Наконец, я не могу использовать Proxy
потому что FrameworkProvidedDependencyResolver
не interface
.
Хорошо, переформулирую мой вопрос:
Можно ли предоставить реализацию класса с использованием пользовательского ClassLoader
, который будет правильно использоваться из статического контекста? Возможно, у меня есть класс на его собственном уникальном пути с уникальным именем, которое я могу редактировать по мере его загрузки, чтобы он отображался в JVM с ожидаемым путем и именем, которые я пытаюсь переопределить? Любое другое решение, конечно, приветствуется.