Principe
Lors du développement d’une application web (en Java ou pas) il est conseillé de créer des classes de tests afin de vérifier premièrement que le code écrit correspondant bien aux spécifications fonctionnelles demandées, et deuxièmement que la fonctionnalité (ou un ensemble de fonctionnalités) reste valide lorsque des demandes d’évolution sont intégrées à l’application.

Ces classes de tests nous permettent donc rapidement de savoir que le code à livrer est cohérent et correspondant aux spécifications fonctionnelles.
La bibliothèque JUnit est faite pour réaliser ce genre de tests dans un environnement Java, la version utilisée dans l’exemple est la 4.8.1.
(il est cependant rare que le développeur ait le temps d’écrire une classe de test pour chaque fonctionnalité qu’il code, par manque de temps principalement)
Besoins
Nous devons effectuer des tests unitaires de base de données en utilisant des services métier définis dans des fichiers de configurations Spring. Ces services métier accèdent à la base de données via une variable JNDI (Java Naming and Directory Interface) déclaré à la fois au sein de notre conteneur J2EE (serveur d’applications) et dans notre application de la façon suivante :
<!-- ###### JNDI Lookup ###### --> <bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName" value="jdbc/dbGeeks" /> </bean>
Comment lancer nos tests en mode batch (hors conteneur J2EE) utilisant notre fichier de contexte Spring déclaré ci-dessus sachant qu’il utilise un contexte JNDI propre au serveur d’application ?
Tel est l’enjeu de cet article, deux solutions s’offrent à nous :
- La première consiste à utiliser l’attribut defaultObject de la classe JndiObjectFactoryBean qui permet de changer de source de données lorsque l’appel JNDI échoue
- La seconde consiste à créer une classe qui va créer pour nous le contexte JNDI avant d’utiliser nos services dans les fichiers de configuration Spring
Base de données
- Les tests réalisés dans l’exemple permettent simplement de se connecter à une base de données et de récupérer le contenu d’une table ‘user’ afin de vérifier que l’accès en base se fait correctement. La base de données utilisée est MySQL version 5.1.41.
- Script de création de la table
CREATE TABLE IF NOT EXISTS `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `login` varchar(50) NOT NULL, `pass` varchar(200) NOT NULL, `address` varchar(500) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `login` (`login`) ) ENGINE=InnoDB;
Solution 1
La première solution consiste donc à passer vers une source de données de substitution lorsque l’appel JNDI échoue, tout se passe au niveau de la configuration Spring. Les paramètres de la source de données « local » prend donc des paramètres en dur ou par le biais d’un fichier de properties :
- fichier de configuration Spring
<!-- ###### JNDI Lookup ###### --> <bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName" value="jdbc/dbGeeks" /> <!-- fallback to a local datasource if we are not in the container --> <property name="defaultObject" ref="localDataSource" /> </bean> <bean id="localDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/des_geeks?autoReconnect=true" /> <property name="username" value="root" /> <property name="password" value="" /> </bean>
- classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation
public class TestUserServiceCase1 extends TestCase {
UserService userService;
@Override
protected void setUp() throws Exception {
super.setUp();
String[] springFiles = { "applicationContext-metier-case1.xml" };
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springFiles);
userService = (UserService) applicationContext.getBean("userService");
}
public void testGetAllUsers() throws Exception {
List<UserBean> usersList = userService.getAllUsers();
System.out.println(usersList);
}
}
Solution 2
La seconde solution consiste à référencer le contexte JNDI avant l’instanciation des objets Spring, ce qui permet de ne pas toucher du tout aux fichiers de configurations Spring, une classe Java fait l’affaire :
- fichier de configuration Spring d’origine
<!-- ###### JNDI Lookup ###### --> <bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName" value="jdbc/dbGeeks" /> </bean>
- classe Java permettant d’initialiser le contexte JNDI
public class JndiDatasourceCreator {
/** constants datasource */
private static final String url = "jdbc:mysql://localhost:3306/des_geeks?autoReconnect=true";
private static final String username = "xxxx";
private static final String password = "yyyy";
private static final String jndiName = "dbGeeks";
public static void create() throws Exception {
try {
// initialisation du contexte
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
InitialContext context = new InitialContext();
// création d'une référence sur la DataSource
Reference reference = new Reference("javax.sql.DataSource", "org.apache.commons.dbcp.BasicDataSourceFactory", null);
reference.add(new StringRefAddr("driverClassName", "com.mysql.jdbc.Driver"));
reference.add(new StringRefAddr("url", url));
reference.add(new StringRefAddr("username", username));
reference.add(new StringRefAddr("password", password));
// liaison de la DataSource au contexte
context.rebind("jdbc/" + jndiName, reference);
} catch (NamingException ex) {
ex.printStackTrace();
}
}
}
- classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation
public class TestUserServiceCase2 extends TestCase {
UserService userService;
@Override
protected void setUp() throws Exception {
super.setUp();
JndiDatasourceCreator.create();
String[] springFiles = { "applicationContext-metier-case2.xml" };
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springFiles);
userService = (UserService) applicationContext.getBean("userService");
}
public void testGetAllUsers() throws Exception {
List<UserBean> usersList = userService.getAllUsers();
System.out.println(usersList);
}
}
Conclusion
C’est important de pouvoir lancer ces test unitaires ou d’intégration en se détachant complètement du serveur d’applications. La mise en place d’une de ces méthodes permet donc de faire cela tranquillement sans avoir à bouleverser votre code (surtout la seconde solution).



{ 5 comments… lisez-les, puis ajoutez le vôtre ! }
Avec tout ça c’est quand que tu nous ponds un super site ?!
J’y travaille
et ce sera un site sur magic the gathering ^^
Article mis à jour, j’avais oublié de vous présenter la classe Java permettant de réaliser l’initialisation du contexte JNDI dans la seconde solution.
Néanmoins tout se trouve dans le source, ++
Super ! Mais j’ai eu l’impression qu’il fallait mieux introduire la classe « com.sun.jndi.fscontext.RefFSContextFactory » utilisée dans la deuxième solution.
Bonjour Rodrigo, comme toi la seconde solution me parait plus correcte en considérant la première solution plus comme une astuce qu’autre chose
néanmoins les deux solutions sont viables c’est pourquoi j’ai voulu les présenter dans cet article, merci à toi d’être passé, au plaisir.