如何使用Spring测试模拟的JNDI数据源?

35

我对Spring还比较新,想知道如何创建使用模拟数据源的JUnit测试,并且如何在其中使用JNDI上下文?目前我的应用程序使用Tomcat中的JNDI上下文来检索连接,并通过该连接从数据库检索数据。所以我想必须模拟JNDI调用和数据检索。有没有好的指导建议可以帮助解决这个问题!非常感谢!

8个回答

36

2
我已经尝试过了,但仍然出现异常。异常信息为:javax.naming.NoInitialContextException: 需要在环境变量、系统属性、小程序参数或应用程序资源文件中指定类名:java.naming.factory.initial。 - fastcodejava

33

我通常会在一个单独的文件中定义我的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调用返回的对象:


我已经这样做了,但仍然出现异常:Caused by: javax.naming.NoInitialContextException: 需要在环境或系统属性中指定类名,或作为小程序参数,或在应用程序资源文件中指定:java.naming.factory.initial。 - fastcodejava
@fastcodejava 你具体做了什么?使用了单独的文件来处理JNDI相关配置吗?在测试设置中创建了JNDI上下文吗?还是使用了SimpleNamingContextBuilder - Roadrunner
不幸的是,到Oracle博客文章的链接似乎已经失效了。我找不到一个等价的替代品... - Nicktar

4
您可以通过扩展Spring的AbstractDataSource来创建自己的模拟数据源。
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连接查找和其他部分分开。将DataSource注入到您的数据访问对象(DAO)中,并使用MockDataSource来测试DAOs。

如果我注入数据源,这样不就消除了需要进行JNDI查找的必要性吗? - Marco
可以的。在Spring中有许多获取DataSource的方法。一旦你拥有了它,就可以注入它。不过,Spring也可以从JNDI中读取DataSource。 - Paul Croarkin
1
我编辑了你的答案,去掉了第一行的缩进。现在语法高亮可以正常工作了。希望你不介意。 - Grzegorz Oledzki

2
您可以始终创建一个名为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 Bean中,声明引用。
<jee:jndi-lookup expected-type="javax.sql.DataSource" id="mysqlDataSource" jndi-name="jdbc/mysql"/>

在您的测试bean中,声明数据源。
<bean id="mysqlDataSource" ...>
...
</bean>

记住将测试数据源 bean 移动到测试文件夹中。

1
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>

在其他配置文件中定义的bean应该引用dataSourceFromJndi bean。

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

这种方法的优点是您可以保留2个不同的数据库配置文件-一个用于生产环境,另一个用于单元测试。只需导入正确的配置文件即可。测试配置将包含一个默认对象。

1

最近我遇到了模拟JNDI DB资源用于JUnit测试案例的问题。我通过创建一个名为DBStub的单独类来处理,该类包含模拟的javax.sql.DataSource,并将其分配给Spring简单实现的JNDI命名上下文构建器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());
    }
}

0

您还可以使用Simple-JNDI。它是一个内存中的JNDI实现,可用于在J2EE容器之外处理JNDI上下文。它允许您在生产和测试中使用相同的bean定义文件。假设这是您在生产中的bean定义:

<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文件放入您的类路径中。然后像往常一样访问您的数据源。

此处了解更多关于Simple-JNDI的信息。


0

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);
}

}

数据库配置存根

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());
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接