JUnit : Test unitaire hors conteneur J2EE avec Spring et JNDI

 Article modifié dernièrement le 30 Déc 2012 @ 13 h 53 min

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 :

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 :

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

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
  • classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation

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
  • classe Java permettant d’initialiser le contexte JNDI
  • classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation

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).

Sources disponibles ici

Trois flèches vers le bas

1- Logiciel de brouillage d’adresse IP :

Contourner la censure en surfant anonyme

2- L’article explicatif :

La différence entre un proxy et un VPN

3- Comment espionner un smartphone (app) :

L’application de référence

Commentez ici

  • Greg 19 janvier 2010, 21 09

    Avec tout ça c’est quand que tu nous ponds un super site ?!

  • Mimie 19 janvier 2010, 22 10

    J’y travaille 🙂 et ce sera un site sur magic the gathering ^^

  • Mimie 21 janvier 2010, 8 08

    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, ++

  • Rodrigo Hjort 8 février 2010, 2 02

    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.

  • Mimie 8 février 2010, 9 09

    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.

  • omar 20 janvier 2011, 17 05

    Voici une autre solution que j’utilise sur un projet :
    Je configure le jndi dans un fichier disons « datasourceContext.xml » sans oublier de lui donner un alias.
    Exemple :

    java:comp/env/jdbc/AppDB

    Dans les tests unitaire j’importe un autre fichier xml: dataSourceTest.xml à la palce du précédent

    Concernant la seconde solution proposée, elle a l’inconvénient d’internaliser la configuration dans du code java

    java:comp/env/jdbc/appDB

    Pour mes tests unitaires, je ne charge pas ce fichier mais un autre datasourceTest.xml

  • Mimie 20 janvier 2011, 23 11

    @omar: ton argument sur la deuxième solution ne tient pas étant donné que la classe Java peut faire appel à un fichier de propriétés (.properties) externe qui sera pour le coup équivalent à un fichier de configuration .xml de Spring.
    De plus je n’ai pas très bien saisi ta solution personnelle, le fait d’avoir 2 fichiers distinct change quoi réellement ? quel est le contenu de ces deux fichiers ?

  • omar 21 janvier 2011, 21 09

    Tu dis « la classe Java peut faire appel à un fichier de propriétés (.properties) externe qui sera pour le coup équivalent à un fichier de configuration .xml de Spring. »

    Or mon commentaire faisait référence uniquement à la classe JndiDatasourceCreator
    qui peut etre pourrait utiliser un properties mais ce n’est pas le cas dans l’exemple où les paramètres sont codés en static.

    Mon jndi est défini dans un fichier datasourceContext.xml :
    [xml]
    <bean id="tomcatDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="resourceRef" value="true"/>
    <property name="jndiName">
    <value>java:comp/env/jdbc/ScribeDB</value>
    </property>
    </bean>
    <alias alias="dataSource" name="tomcatDataSource"/>
    [/xml]

    Et durant les tests unitaires, j’importe en lieu et place de ce
    dernier un dataSourceTest.xml au contenu suivant :
    [xml]
    <!–
    Création de la dataSource
    ici une connexion jdbc vers la base données
    –>
    <bean id="testDataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.postgresql.Driver"/>
    <property name="url" value="%{dataSource.url}"/>
    <property name="username" value="%{dataSource.username}"/>
    <property name="password" value="%{dataSource.password}"/>
    </bean>
    <alias alias="dataSource" name="testDataSource"/>
    [/xml]

    Voila.

  • Mimie 21 janvier 2011, 21 09

    @omar: pour afficher du code il suffit d’utiliser les balises suivantes :
    [plain]
    [xml] … [/xml], [java] … [/java], etc.
    [/plain]
    – Ton premier point est juste, seulement je pensais que tu n’appréciais pas la seconde méthode parce que justement il fallait mettre en dur les paramètres, je voulais juste souligner qu’un .properties pouvait palier à ce problème.
    – Pour ta séparation des fichiers de config Spring je comprends mieux ce que tu fais, cependant à quoi te sert ton alias « dataSource » ? comment l’exploites-tu dans ton appel Java ? peux-tu nous laisser un exemple de ton code Java ?

    Merci d’être repassé pour les précisions 🙂

  • omar 22 janvier 2011, 12 12

    Salut,
    Je n’a pas précisé ou était injecté le dataSource car l’article lui-même ne le précisait pas comment
    dbGeeks était injecté dans le UserService.

    Sinon l’alias dataSource est injecté ainsi :
    [xml]
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" depends-on="dataSource">
    <description>
    Factory hibernate qui va nous permettre de récupérer des sessions.
    On lui passe la "underlying jdbc datasource" (lien jdbc avec la base)
    ainsi que le gestionnaire de transactions (pas besoin de JTA ici
    puisqu’on a une seule source de donnée, la base postgres).
    </description>
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation">
    <value>classpath:hibernate.cfg.xml</value>
    </property>
    </bean>
    [/xml]

    Enfin la sessionFactory est injecté dans le constructeur un kebabDao qui lui-même est utilisé dans un kebabService.

    Une autre variante plus simple est au lieu d’importer des fichiers de configuration différents (ici un dataSourceTest en test ), c’est d’importer le fichier utilisé par le serveur & de redéfinir le bean du même nom.

    Ceci est possible en important un autre fichier
    qui possède un bean du même nom. Et le dernier qui est trouvé par Spring est retenu.

    Exemple: un test unitaire importe :
    – appplicationContext.xml qui lui importe tout (dont un bean appelé monSuperDataSource)
    – plus un dataSourceTest.xml qui définit un bean appelé monSuperDataSource. Etant le dernier importé du meme nom, c’est lui qui sera injecté par Spring.

    C’est aussi utile pour importer par redéfinition des beans mockés qui n’attaquent pas un ldap, si externe etc. mais retourne des valeurs quelconques car hors scope du test.

  • Mimie 22 janvier 2011, 13 01

    Merci omar, je vois que nous faisons la même chose, c’est rassurant.

  • Applicius 8 juillet 2013, 10 10

    Pour les tests JDBC, Acolyte a été crée il n’y a pas longtemps pour ça : http://www.applicius.fr/tests-unitaires-persistance-2/374

Article suivant:

Article précédent:

Share This