I am fairly new to Spring and wondering how to create JUnit tests that use a mocked datasource and how to use a JNDI context with that? Currently my application uses a JNDI context from tomcat to retrieve a connection and via that connection retrieves data from a database. So I guess I need to mock the JNDI calls and the data retrieval. Any good pointers on what the best way to tackle this would be great! Thanks a lot!
I usually define my JNDI dependencies in seperate file, like 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>
So that in test resources I can create another file and define the test datasource however it suits me, like 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>
And then in my test class I use the test configuration of the datasource instead of production one that depends on JNDI:
@ContextConfiguration({
"classpath*:META-INF/spring/datasource-testcontext.xml",
"classpath*:META-INF/spring/session-factory-context.xml"
})
public class MyTest {
}
If the data source is not defined in a separate file You can still stub the object returned by JNDI calls easily:
- like this: Injecting JNDI datasources for JUnit Tests outside of a container
- or using classes in package
org.springframework.mock.jndi
, ie.SimpleNamingContextBuilder
(there's an example in the javadoc of this calass).
SimpleNamingContextBuilder
? –
Aldous You can use SimpleNamingContextBuilder to make a jndi datasource available to your tests:
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("java:comp/env/jdbc/mydatasource", dataSource);
builder.activate();
This isn't exactly mocking the datasource, but it does make the datasource available via jndi for your tests.
I usually define my JNDI dependencies in seperate file, like 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>
So that in test resources I can create another file and define the test datasource however it suits me, like 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>
And then in my test class I use the test configuration of the datasource instead of production one that depends on JNDI:
@ContextConfiguration({
"classpath*:META-INF/spring/datasource-testcontext.xml",
"classpath*:META-INF/spring/session-factory-context.xml"
})
public class MyTest {
}
If the data source is not defined in a separate file You can still stub the object returned by JNDI calls easily:
- like this: Injecting JNDI datasources for JUnit Tests outside of a container
- or using classes in package
org.springframework.mock.jndi
, ie.SimpleNamingContextBuilder
(there's an example in the javadoc of this calass).
SimpleNamingContextBuilder
? –
Aldous You can create your own mock DataSource by extending Spring's 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;
}
}
I'd separate the JNDI lookup of the connection from the rest of the code. Inject the DataSource into your Data Access Objects (DAOs) and use the MockDataSource for testing the DAOs.
You can allways create a beans.test.xml configuration, where you first reference the beans.xml, and then override the datasource configuration:
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 Test Class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/beans.test.xml" })
public class ASRTests
{
...
}
In your jndi bean, declare the reference
<jee:jndi-lookup expected-type="javax.sql.DataSource" id="mysqlDataSource" jndi-name="jdbc/mysql"/>
In your test bean, declare the datasource
<bean id="mysqlDataSource" ...>
...
</bean>
Keep in mind to move the test datasource bean into test folder.
Spring's org.springframework.jndi.JndiObjectFactoryBean
is best suited for JNDI lookups. As per its documentation, it allows injecting default values as well for spring based test cases.
Refer to the below spring configuration (named as 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>
Add bean defined on other configuration file shall refer to dataSourceFromJndi
bean
<!-- START OF SERVICES -->
<bean class="com.sample.Service" >
<property name="dataSource" ref="dataSourceFromJndi" />
</bean>
The advantage of this approach is that you can keep 2 different DB configuration files - one for production and other for unit testing. Just import the correct one. Test configuration will contain a default object.
I recently encountered the problem of mocking JNDI DB resource for my JUnit test case. I dealt with created a separate DBStub class, that contains mocked javax.sql.DataSource and assign it to the Spring simple implementation of 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();
}
}
}
Extend this class to the actual JUnit test class would resolve the issue,
@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());
}
}
Java Config.....
Junit Test Case
@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());
}
}
You can also use Simple-JNDI. It is an in-memory JNDI Implementation for working with JNDI contexts outside of a J2EE container. It lets you use the same bean definition file in production and test. Assume this is your bean definition in production:
<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>
Create a property file like this
type=javax.sql.DataSource
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost/testdb
username=user_name
password=password
Put Simple-JNDI and a jndi.properties file with a little configuration in your classpath. Then access your datasource as usual.
© 2022 - 2024 — McMap. All rights reserved.