Как подделать InitialContext с помощью конструктора по умолчанию
Все,
Я пытаюсь выполнить некоторые модульные тесты в каком-то архаичном Java-коде (без интерфейсов, без абстракции и т.д.)
Это сервлет, который использует ServletContext (который я предполагаю, настроен Tomcat), и он имеет информацию о базе данных, которая настроена в файле web.xml/context.xml. Теперь я понял, как сделать Fake ServletContext, но код имеет
InitialContext _ic = new InitialContext();
по всему месту (поэтому заменить его не представляется возможным). Мне нужно найти способ сделать по умолчанию InitialContext() способным сделать _ic.lookup(val)
без исключения исключения.
Я предполагаю, что есть некоторый способ загрузки context.xml, но как работает эта магия, я рисую пробел. У кого-нибудь есть идеи?
Ответы
Ответ 1
Вы можете использовать PowerMock, чтобы искупить построение InitialContext и контролировать его поведение. Конструктор Mocking документирован здесь.
Тесты PowerMock могут быть довольно грязными и сложными, рефакторинг обычно является лучшим вариантом.
Ответ 2
Воспользуйтесь тем фактом, что InitialContext
использует SPI для управления его созданием. Вы можете подключиться к его жизненному циклу, создав реализацию javax.naming.spi.InitialContextFactory
и передав ее тестам через системное свойство javax.naming.factory.initial
(Context.INTITIAL_CONTEXT_FACTORY
). Это проще, чем кажется.
Учитывая этот класс:
public class UseInitialContext {
public UseInitialContext() {
try {
InitialContext ic = new InitialContext();
Object myObject = ic.lookup("myObject");
System.out.println(myObject);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
И это подразумевает 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;
}
}
Создание экземпляра UseInitialContext
в тесте junit с помощью
-Djava.naming.initial.factory=initial.context.test.MyInitialContext
на выходах командной строки This is my object!!
(легко настроить в затмении). Мне нравится Mockito за насмешки и огрызки. Я бы также порекомендовал Micheal Feather эффективно работать с устаревшим кодом, если вы имеете дело с большим количеством устаревшего кода. Это все о том, как найти швы в программах, чтобы выделить отдельные части для тестирования.
Ответ 3
Здесь мое решение для настройки Inintial Context для моих модульных тестов. Сначала я добавил следующую зависимость теста к моему проекту:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
<version>6.0.33</version>
<scope>test</scope>
</dependency>
Затем я создал статический метод со следующим кодом:
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);
}
Наконец, в каждом из моих тестовых классов я добавляю метод @BeforeClass, который вызывает setupInitialContext.
Ответ 4
Попробуйте настроить системные переменные до:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,
"org.apache.naming");
InitialContext ic = new InitialContext();
Если вы используете JUnit, следуйте этому документу: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit
Ответ 5
Сегодня я столкнулся с той же проблемой (мы не можем использовать PowerMock) и решил ее так:
-
Не выполняйте поиск в конструкторе, поэтому, когда вы вызываете @InitMock на объект, конструктор еще не требует контекста.
-
Создайте метод для извлечения службы bean, если необходимо, как "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");
}
- В тесте установите его:
@Mock
ApplicationService applicationServiceBean;
@Mock
InitialContext ic;
@InjectMocks
ApplicationResourceProvider arp;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(ic.lookup(anyString())).thenReturn(applicationServiceBean);
...
}
Ответ 6
Вы считали mockito?
Это просто:
InitialContext ctx = mock (InitialContext.class);
Кстати, если вы решите использовать макеты, я бы порекомендовал также прочитать эту статью: http://martinfowler.com/articles/mocksArentStubs.html
Ответ 7
Бедная автономная реализация без внешних библиотек:
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*/
...
Вы можете использовать switch
в переопределении метода lookup
, чтобы вернуть ожидаемое значение для каждого отдельного значения val
, переданного _ic.lookup(val)
в рамках прежней программы.