Статьи

Макетирование LDAP / JNDI в модульных тестах

При модульном тестировании класса, который запрашивает сервер LDAP с использованием API JNDI Java, мне нужно было заменить реальный удаленный сервер LDAP на фиктивный уровень доступа LDAP, чтобы модульный тест (помните, это не интеграционный тест) не зависел от каких-либо внешний SW / HW. Несколько часов поиска в Google не дали никакой подходящей реализации, поэтому мне пришлось создать свою собственную. В конце концов, это оказалось легкой задачей. Я надеюсь, что это может быть полезно и для вас.

Для создания тестовой реализации LDAP / JNDI вам необходимо:

  1. Подключите реализацию JNDI к JVM и убедитесь, что вы ее используете
  2. Реально реализовать уровень JNDI путем реализации / пересмешивания нескольких (3) классов JNDI, в основном из пакета javax.naming.directory.
  3. Сконфигурируйте макетную реализацию для возврата ожидаемых данных

1. Настройка JVM для использования тестовой реализации JNDI

Лучший способ «внедрить» вашу тестовую реализацию LDAP / JNDI зависит от того, как ваш код обращается к ней. Есть в основном два варианта:

  1. Вы явно указываете реализацию для использования через параметр INITIAL_CONTEXT_FACTORY
  2. Вы используете реализацию по умолчанию для JVM

Давайте посмотрим на пример:

new javax.naming.directory.InitialDirContext(new Hashtable(){{ put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); put(Context.PROVIDER_URL, "ldap://ldap.example.com:389");}});

Javax.naming.directory.InitialDirContext делегирует большинство операций фактической реализации, которая предоставляется запрошенной исходной фабрикой контекста, если строка # 2 включена, или основана на конфигурации JVM — см.  NamingManager.getInitialContext (..) . Следовательно:

  1. Если в вашем коде явно указана начальная фабрика контекста , сконфигурируйте ее так, чтобы она использовала тестовую реализацию начальной фабрики контекста , т.е. вы изменяете код на что-то вроде put (Context.INITIAL_CONTEXT_FACTORY, «your.own.MockInitialDirContextFactory») (у вас есть эта настраиваемая, правильно ?)
  2. Если ваш код полагается на конфигурацию JVM для обеспечения правильной реализации, сконфигурируйте его с помощью пользовательского InitialContextFactoryBuilder , который будет возвращать вашу тестовую реализацию исходной фабрики контекста. Я не буду вдаваться в подробности, вы можете увидеть пример в макете Spring jndi SimpleNamingContextBuilder [ source ] (к сожалению, он высмеивает только javax.naming, а не javax.naming.directory, который нам нужен для LDAP)

2. Реализация / насмешливые классы JNDI

Тестовая реализация LDAP через JNDI довольно проста. Нам нужно:

  1. InitialContextFactory для создания наших тестовых контекстов, как описано выше
  2. Сама тестовая реализация DirContext , которую мы будем издеваться, используя Mockito (интерфейс имеет много методов для реализации, в то время как мой код использует только один из них)
  3. И реализация NamingEnumeration для возврата результатов поиска из метода поиска фиктивного DirContext

Фабрика начального контекста теста очень проста:

public class MockInitialDirContextFactory implements InitialContextFactory {private static DirContext mockContext = null;/** Returns the last DirContext (which is a Mockito mock) retrieved from this factory. */public static DirContext getLatestMockContext() {return mockContext;}public Context getInitialContext(Hashtable environment)throws NamingException {synchronized(MockInitialDirContextFactory.class) {mockContext = (DirContext)Mockito.mock(DirContext.class);}return mockContext;}}

Мы храним последний макет DirContext (тестируемый класс создает только один, так что этого достаточно), чтобы мы могли сказать ему, какие вызовы ожидать и что возвращать (то есть делать некоторую «заглушку»).

Нам также нужна реализация NamingEnumeration, которая возвращается различными методами поиска. Поскольку мы на самом деле не используем его, мы также можем смоделировать его с помощью Mockito (простого Mockito.mock (NamingEnumeration.class) будет достаточно, чтобы заменить все строки ниже), но я решил создать реальную реализацию, чтобы в более сложных тестах его можно расширить, чтобы фактически иметь возможность хранить и возвращать некоторые поддельные данные поиска LDAP.

В этом случае NamingEnumeration должен содержать экземпляры конкретного класса SearchResult с фактическими данными LDAP в своем поле атрибутов типа , для которого мы можем использовать конкретную реализацию BasicAttributes, предоставляемую JVM. Но сейчас давайте просто вернем пустое перечисление.

public class MockNamingEnumeration/*<SearchResult>*/ implements NamingEnumeration {public void close() throws NamingException {}public boolean hasMore() throws NamingException {return hasMoreElements();}public Object next() throws NamingException {return nextElement();}public boolean hasMoreElements() {return false;}public Object nextElement() {return null;}}

Как видите, эта реализация будет вести себя так, как будто поиск не соответствует ни одной записи.

3. Использование тестовой реализации LDAP / JNDI

Последний фрагмент — это фактический тест JUnit гипотетического класса TestedLdapReader, который ищет сервер LDAP:

 

public class MyMockLdapTest extends TestCase {private TestedLdapReader ldapReader;        ...protected void setUp() throws Exception {super.setUp();ldapReader = new TestedLdapReader();ldapReader.setInitialContextFactory(MockInitialDirContextFactory.class.getName());ldapReader.setLdapUrl("ldap://thisIsIgnoredInTests");}public void testLdapSearch() throws Exception {ldapReader.initLdap(); // obtains an InitialDirContext...final DirContext mockContext = MockInitialDirContextFactory.getLatestMockContext(); //Stub the public NamingEnumeration search(String name, String filter, SearchControls cons)Mockito.when( mockContext.search( (String) Mockito.eq("ou=bluepages,o=ibm.com") , Mockito.anyString() , (SearchControls) Mockito.any(SearchControls.class))) // a custom 'answer', which records the queries issued .thenAnswer(new Answer() { public Object answer(InvocationOnMock invocation) throws Throwable { LOG.debug("LDAP query:" + invocation.getArguments()[1] ); return new MockNamingEnumeration(); } }); try { ldapReader.searchLdap(); } catch (Exception e) { LOG.warn("exception during execution", e); } // Uncomment to find out the methods called on the context: // Mockito.verifyNoMoreInteractions(new Object[]{mockContext});}

Давайте подведем итоги, что мы делаем здесь:

  • # 07,08: Мы говорим тестируемому классу использовать нашу тестовую реализацию JNDI
  • # 13: Предполагается, что этот вызов создает экземпляр InitialDirContext, предоставляя ему начальный параметр класса фабрики контекста, установленный в строках 07-08
  • # 16-26: Мы используем Mockito, чтобы сконфигурировать фиктивный DirContext для ожидания поискового вызова для контекста «ou = bluepages, o = ibm.com», любой строки запроса и любых элементов управления поиском и сообщаем ему возвращать пустой MockNamingEnumeration, а также регистрация фактического запроса LDAP (2-й аргумент).
  • # 29: Испытанный метод называется
  • # 35: Если мы не уверены, какие методы вызывает тестируемый метод для DirContext, мы можем раскомментировать эту строку, чтобы позволить Mockito проверить ее (добавив Mockito.verify (mockContext. <Имя метода> (..)) до # 35 для каждый метод, о котором мы уже знаем)

Резюме

Мы создали минималистскую реализацию LDAP поверх JNDI, используя частично реальные и частично фиктивные объекты. Его можно легко расширить, чтобы можно было сконфигурировать данные, возвращаемые из отдельных поисков LDAP (в настоящее время мы всегда возвращаем пустую коллекцию) и, таким образом, тестировать поведение в ответ на различные наборы данных. Конечно, осталось место для упрощения.

 

 

 

От http://theholyjava.wordpress.com/2010/05/05/mocking-out-ldapjndi-in-unit-tests/