Как протестировать фальсифицированный источник данных JNDI с помощью Spring?

#spring #testing #junit #mocking #datasource

#spring #тестирование #junit #издевательство #источник данных

Вопрос:

Я довольно новичок в Spring и задаюсь вопросом, как создавать тесты JUnit, которые используют издевательский источник данных, и как использовать контекст JNDI с этим? В настоящее время мое приложение использует контекст JNDI из tomcat для получения соединения и через это соединение извлекает данные из базы данных. Итак, я думаю, мне нужно издеваться над вызовами JNDI и извлечением данных. Любые хорошие указания на то, какой лучший способ решить эту проблему, были бы великолепны! Большое спасибо!

Ответ №1:

Вы можете использовать SimpleNamingContextBuilder, чтобы сделать источник данных jndi доступным для ваших тестов:

     SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
    builder.bind("java:comp/env/jdbc/mydatasource", dataSource);
    builder.activate();
  

https://fisheye.springsource.org/browse/spring-framework/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java?hb=true

Это не совсем издевательство над источником данных, но это делает источник данных доступным через jndi для ваших тестов.

Комментарии:

1. Я сделал это, но я все еще получаю исключение, вызванное: javax.naming. Исключение NoInitialContextException: необходимо указать имя класса в свойстве среды или системы, или в качестве параметра апплета, или в файле ресурсов приложения: java.naming.factory.initial

Ответ №2:

Обычно я определяю свои зависимости JNDI в отдельном файле, например datasource-context.xml :

 <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

    <jee:jndi-lookup id="dataSource" 
        jndi-name="java:comp/env/dataSource" 
        expected-type="javax.sql.DataSource" />

</beans>
  

Чтобы в тестовых ресурсах я мог создать другой файл и определить тестовый источник данных так, как мне подходит, например datasource-testcontext.xml :

 <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource"
        p:driverClassName="org.hsqldb.jdbcDriver"
        p:url="jdbc:hsqldb:hsql://localhost:9001"
        p:username="sa"
        p:password="" /> 

</beans>
  

И затем в моем тестовом классе я использую тестовую конфигурацию источника данных вместо рабочей, которая зависит от JNDI:

 @ContextConfiguration({
    "classpath*:META-INF/spring/datasource-testcontext.xml",
    "classpath*:META-INF/spring/session-factory-context.xml"
})
public class MyTest {

}
  

Если источник данных не определен в отдельном файле, вы все равно можете легко заглушить объект, возвращаемый вызовами JNDI:

Комментарии:

1. Я сделал это, но я все еще получаю исключение, вызванное: javax.naming. Исключение NoInitialContextException: необходимо указать имя класса в свойстве среды или системы, или в качестве параметра апплета, или в файле ресурсов приложения: java.naming.factory.initial

2. @fastcodejava Что именно вы сделали? Использовался отдельный файл для конфигураций, связанных с JNDI? Созданный контекст JNDI в тестовой настройке? Или используется SimpleNamingContextBuilder ?

3. к сожалению, ссылка на сообщение в блоге oracle кажется мертвой. Я не могу найти эквивалентную замену…

Ответ №3:

Вы можете создать свой собственный макет источника данных, расширив AbstractDataSource Spring.

 import java.sql.Connection;
import java.sql.SQLException;

import org.springframework.jdbc.datasource.AbstractDataSource;

/**
 * Mock implementation of DataSource suitable for use in testing.
 * 
 *
 */
public class MockDataSource extends AbstractDataSource {
    private Connection connection;

    /**
     * Sets the connection returned by javax.sql.DataSource#getConnection()
     * and javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
     * 
     * @param connection
     */
    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    /*
     * (non-Javadoc)
     * @see javax.sql.DataSource#getConnection()
     */
    public Connection getConnection()
            throws SQLException {
        return connection;
    }

    /*
     * (non-Javadoc)
     * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
     */
    public Connection getConnection(String username, String password)
            throws SQLException {
        return connection;
    }
}
  

Я бы отделил поиск соединения в JNDI от остальной части кода. Внедрите источник данных в ваши объекты доступа к данным (DAO) и используйте MockDataSource для тестирования DAO.

Комментарии:

1. Если я введу источник данных, не устранит ли это необходимость поиска в JNDI?

2. Это могло бы. В Spring есть несколько способов получить источник данных. Как только у вас это будет, вы сможете внедрить. Однако Spring может считывать источник данных из JNDI.

3. Я отредактировал ваш ответ, чтобы убрать отступ в первой строке. Теперь подсветка синтаксиса работает. Я надеюсь, вы не возражаете.

Ответ №4:

Вы всегда можете создать beans.test.xml конфигурация, где вы сначала ссылаетесь на beans.xml, а затем переопределить конфигурацию источника данных:

src/main/resources/beans.xml

 <!-- Database configuration -->
<import resource="beans.datasource.jndi.xml" />
  

src/test/resources/beans.test.xml

 <import resource="beans.xml" />
<import resource="beans.datasource.test.xml" />
  

Тестовый класс JUnit:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/beans.test.xml" })
public class ASRTests
{
...
}
  

В вашем компоненте jndi объявите ссылку

 <jee:jndi-lookup expected-type="javax.sql.DataSource" id="mysqlDataSource" jndi-name="jdbc/mysql"/>
  

В вашем тестовом компоненте объявите источник данных

 <bean id="mysqlDataSource" ...>
...
</bean>
  

Не забывайте перемещать компонент test datasource в папку test.

Ответ №5:

Spring org.springframework.jndi.JndiObjectFactoryBean лучше всего подходит для поиска в JNDI. Согласно его документации, он позволяет вводить значения по умолчанию также для тестовых случаев на основе spring.

Обратитесь к приведенной ниже конфигурации spring (названной как spring-test-db-config.xml )

 <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource">
    <property name="URL" value="jdbc:oracle:thin:@localhost:1521:XE"/>
    <property name="user" value="UNITTEST"/>
    <property name="password" value="UNITTEST"/>
</bean>

<bean id="dataSourceFromJndi" class="org.springframework.jndi.JndiObjectFactoryBean">
    <!-- Any junk value will suffice as that is always gonna throw NamingException -->
    <property name="jndiName" value="jdbc/Ds"/>
    <property name="defaultObject" ref="dataSource"/>
</bean>
  

Добавить компонент, определенный в другом файле конфигурации, должен ссылаться на dataSourceFromJndi компонент

 <!-- START OF SERVICES -->
<bean class="com.sample.Service" >
    <property name="dataSource" ref="dataSourceFromJndi" />
</bean>
  

Преимущество этого подхода в том, что вы можете хранить 2 разных файла конфигурации БД — один для производства, а другой для модульного тестирования. Просто импортируйте правильный. Тестовая конфигурация будет содержать объект по умолчанию.

Ответ №6:

Недавно я столкнулся с проблемой издевательства над ресурсом JNDI DB для моего тестового примера JUnit. Я имел дело с созданием отдельного класса DBStub, который содержит издевательский javax.sql.DataSource и назначил его Spring simple реализации JNDI naming context builder SimpleNamingContextBuilder,

 public class DBStub {

@Mock
DataSource dataSource;

public DBStub() {
    try {
        MockitoAnnotations.initMocks(this);
        SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
        builder.bind("java:comp/env/jdbc/DataSource", dataSource);
    } catch (NamingException e) {
        fail();
    }

    } 
}
  

Расширение этого класса до фактического тестового класса JUnit решило бы проблему,

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:application-context.xml" })
public class PricingOperationTest extends DBStub {

    @Autowired
    private JdbcTemplate template;

    @Test
    public void testDataSource() {
        assertNotNull(template.getDataSource());
    }
}
  

Ответ №7:

Конфигурация Java…..

Тестовый пример Junit

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DatabaseConfigStub.class}, loader= AnnotationConfigContextLoader.class)
public class DatabaseConfigTest  {

@Autowired
private DataSource datasource;
@Autowired
private JdbcTemplate jdbcTemplate;


@Before
public void setUp() throws Exception {

}

@After
public void tearDown() throws Exception {
}

@Test
public void testDataSource() {
    assertNotNull(datasource);
    assertNotNull(jdbcTemplate);
}

}
  

DatabaseConfigStub

 public class DatabaseConfigStub {

private static final Logger log = Logger.getLogger(DatabaseConfigStub.class);

        private static final String DS_NAME = "jdbc/DS_NAME";

@Bean
DataSource dataSource() {
    JndiObjectFactoryBean jndiObjectBean = EasyMock.createMock(JndiObjectFactoryBean.class);
    jndiObjectBean.setJndiName(DS_NAME);
    jndiObjectBean.setResourceRef(true);
    jndiObjectBean.setProxyInterfaces(DataSource.class);

    EasyMock.expect( (DataSource)jndiObjectBean.getObject()).andReturn(new DataSource() {

            public <T> T unwrap(Class<T> iface) throws SQLException {
                // TODO Auto-generated method stub
                return null;
            }

            public boolean isWrapperFor(Class<?> iface) throws SQLException {
                // TODO Auto-generated method stub
                return false;
            }

            public void setLoginTimeout(int seconds) throws SQLException {
                // TODO Auto-generated method stub

            }

            public void setLogWriter(PrintWriter out) throws SQLException {
                // TODO Auto-generated method stub

            }

            public int getLoginTimeout() throws SQLException {
                // TODO Auto-generated method stub
                return 0;
            }

            public PrintWriter getLogWriter() throws SQLException {
                // TODO Auto-generated method stub
                return null;
            }

            public Connection getConnection(String username, String password)
                    throws SQLException {
                // TODO Auto-generated method stub
                return null;
            }

            public Connection getConnection() throws SQLException {
                // TODO Auto-generated method stub
                return null;
            }
        }
    );
    EasyMock.replay(jndiObjectBean);

    return (DataSource) jndiObjectBean.getObject();
}

@Bean
JdbcTemplate jdbcTemplate(){
    return new JdbcTemplate( dataSource());
}
  

}

Ответ №8:

Вы также можете использовать Simple-JNDI. Это реализация JNDI в памяти для работы с контекстами JNDI за пределами контейнера J2EE. Это позволяет использовать один и тот же файл определения компонента в производстве и тестировании. Предположим, что это ваше определение компонента в рабочей среде:

 <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/DataSource"/>
</bean>
<bean id="dao" class="my.Dao">
    <property name="dataSource" ref="dataSource" />
</bean>
  

Создайте файл свойств, подобный этому

 type=javax.sql.DataSource
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost/testdb
username=user_name
password=password
  

Поместите Simple-JNDI и файл jndi.properties с небольшой конфигурацией в свой classpath. Затем получите доступ к своему источнику данных как обычно.

Подробнее о Simple-JNDI читайте здесь.