Je suis assez nouveau dans Spring et je me demande comment créer des tests JUnit utilisant une source de données simulée et comment utiliser un contexte JNDI avec cela. Actuellement, mon application utilise un contexte JNDI de Tomcat pour récupérer une connexion et, via cette connexion, récupère les données d'une base de données. Je suppose donc que je dois me moquer des appels JNDI et de la récupération des données. N'importe quel bon indicateur de la meilleure façon de s'attaquer à ce problème serait formidable! Merci beaucoup!
Je définis généralement mes dépendances JNDI dans un fichier séparé, tel que 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>
Ainsi, dans les ressources de test, je peux créer un autre fichier et définir la source de données de test comme bon me semble, comme 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>
Et puis dans ma classe de test I , utilisez la configuration de test de la source de données au lieu de celle de production qui dépend de JNDI:
@ContextConfiguration({
"classpath*:META-INF/spring/datasource-testcontext.xml",
"classpath*:META-INF/spring/session-factory-context.xml"
})
public class MyTest {
}
Si la source de données n'est pas définie dans un fichier séparé, vous pouvez toujours remplacer l'objet renvoyé par les appels JNDI facilement:
org.springframework.mock.jndi
, c'est-à-dire. SimpleNamingContextBuilder
(il y a un exemple dans le javadoc de ce calass).Vous pouvez utiliser SimpleNamingContextBuilder pour rendre une source de données jndi disponible pour vos tests:
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("Java:comp/env/jdbc/mydatasource", dataSource);
builder.activate();
Ce n'est pas exactement mocking la source de données, mais cela rend la source de données disponible via jndi pour vos tests.
Vous pouvez créer votre propre source de données fictive en étendant AbstractDataSource de 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;
}
}
Je séparerais la recherche JNDI de la connexion du reste du code. Injectez le DataSource dans vos DAO (Data Access Objects) et utilisez le MockDataSource pour tester les DAO.
Vous pouvez toujours créer une configuration beans.test.xml, dans laquelle vous référencez en premier le beans.xml, puis remplacez la configuration de la source de données:
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" />
Classe de test JUnit:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/beans.test.xml" })
public class ASRTests
{
...
}
Dans votre haricot Jndi, déclarez la référence
<jee:jndi-lookup expected-type="javax.sql.DataSource" id="mysqlDataSource" jndi-name="jdbc/mysql"/>
Dans votre bean test, déclarez la source de données
<bean id="mysqlDataSource" ...>
...
</bean>
N'oubliez pas de déplacer le bean de source de données test dans le dossier test.
Le org.springframework.jndi.JndiObjectFactoryBean
de Spring convient mieux aux recherches JNDI. Selon sa documentation, il permet d'injecter des valeurs par défaut également pour les cas de test basés sur des ressorts.
Reportez-vous à la configuration de ressort ci-dessous (nommée 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>
Ajouter un bean défini sur un autre fichier de configuration doit faire référence à dataSourceFromJndi
bean
<!-- START OF SERVICES -->
<bean class="com.sample.Service" >
<property name="dataSource" ref="dataSourceFromJndi" />
</bean>
L'avantage de cette approche est que vous pouvez conserver deux fichiers de configuration de base de données différents, l'un pour la production et l'autre pour les tests unitaires. Importez juste le bon. La configuration de test contiendra un objet par défaut.
Java Config .....
Cas de test 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());
}
}
Vous pouvez également utiliser Simple-JNDI. Il s'agit d'une implémentation JNDI en mémoire permettant de travailler avec des contextes JNDI en dehors d'un conteneur J2EE. Il vous permet d'utiliser le même fichier de définition de bean en production et en test. Supposons que ceci soit votre définition de haricot en 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>
Créez un fichier de propriétés comme celui-ci
type=javax.sql.DataSource
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost/testdb
username=user_name
password=password
Placez Simple-JNDI et un fichier jndi.properties avec un peu de configuration dans votre chemin de classe. Ensuite, accédez à votre source de données comme d'habitude.