【发布时间】:2011-08-21 21:42:52
【问题描述】:
我是 Spring 的新手,想知道如何创建使用模拟数据源的 JUnit 测试以及如何使用 JNDI 上下文?目前,我的应用程序使用来自 tomcat 的 JNDI 上下文来检索连接,并通过该连接从数据库中检索数据。所以我想我需要模拟 JNDI 调用和数据检索。关于解决这个问题的最佳方法的任何好的指示都会很棒!非常感谢!
【问题讨论】:
标签: spring testing junit mocking datasource
我是 Spring 的新手,想知道如何创建使用模拟数据源的 JUnit 测试以及如何使用 JNDI 上下文?目前,我的应用程序使用来自 tomcat 的 JNDI 上下文来检索连接,并通过该连接从数据库中检索数据。所以我想我需要模拟 JNDI 调用和数据检索。关于解决这个问题的最佳方法的任何好的指示都会很棒!非常感谢!
【问题讨论】:
标签: spring testing junit mocking datasource
您可以通过扩展 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 来测试 DAO。
【讨论】:
我通常在单独的文件中定义我的 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 调用返回的对象:
org.springframework.mock.jndi中的类,即。 SimpleNamingContextBuilder(在这个 calass 的 javadoc 中有一个例子)。【讨论】:
SimpleNamingContextBuilder?
您可以使用 SimpleNamingContextBuilder 使 jndi 数据源可用于您的测试:
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("java:comp/env/jdbc/mydatasource", dataSource);
builder.activate();
这并不完全是模拟数据源,但它确实通过 jndi 使数据源可用于您的测试。
【讨论】:
你总是可以创建一个 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 移动到测试文件夹中。
【讨论】:
Spring 的 org.springframework.jndi.JndiObjectFactoryBean 最适合 JNDI 查找。根据其文档,它还允许为基于弹簧的测试用例注入默认值。
参考下面的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参考dataSourceFromJndibean
<!-- START OF SERVICES -->
<bean class="com.sample.Service" >
<property name="dataSource" ref="dataSourceFromJndi" />
</bean>
这种方法的优点是您可以保留 2 个不同的数据库配置文件 - 一个用于生产,另一个用于单元测试。只需导入正确的。测试配置将包含一个默认对象。
【讨论】:
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());
}
}
【讨论】:
您也可以使用 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 文件放在你的类路径中。然后像往常一样访问您的数据源。
【讨论】:
我最近遇到了为我的 JUnit 测试用例模拟 JNDI DB 资源的问题。我处理创建了一个单独的 DBStub 类,其中包含模拟的 javax.sql.DataSource 并将其分配给 JNDI 命名上下文构建器 SimpleNamingContextBuilder 的 Spring 简单实现,
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());
}
}
【讨论】: