Testez facilement vos DAO en JPA avec Unitils et TestNG

Publié le par stoi

Pour ce premier article, je vais parler de la fonctionnalité la plus cool d'Unitils (à mon sens) : l'injection JPA.

Ingrédients :
  • Unitils : une trousse à outils du test unitaire. Vous ne pourrez probablement plus vous en passer.
  • DBUnit : intégré (magiquement) à Unitils, c'est la clé du système : les jeux de données sont plus faciles à faire, et les tests sont complètements séparés (pas de risque de corrompre les données d'un test à l'autre)
  • TestNG : un framework de test unitaire simplissime à utiliser. (Notez que la recette marche aussi avec JUnit)
  • JPA : oui, c'est ça qu'on souhaite tester.
  • Maven : parce que c'est quand même bien de connaitre les versions des API.
Votre projet (en utilisant Maven, mais ce n'est pas obligatoire) ressemblera à quelque chose comme ça ("packages..." étant l'arborescence de vos packages java) :

| - pom.xml
| - src
     | - main
            | - java
                   | - packages...
                            | - Truc.java
                            | - TrucDao.java
                            | - TrucDaoImpl.java
            | - resources
                   | - META-INF
                            | - persistence.xml
     | - test
            | - java
                   | - packages...
                            | - TrucDaoImplTest.java
            | - resources
                   | - unitils.properties
                   | - META-INF
                            | - persistence-test.xml
                   | - packages...
                            | - TrucDaoImplTest.xml



Jetons un coup d'oeil à l'entité Truc :
 @Entity @Table(name = "TAB_TRUC") public class Truc implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "ID") private Long id; @Column(name = "NAME") private String name; // reste du bean } 

Et TrucDao, un ejb stateless (pourquoi pas) qui expose une méthode findByName() :
 @Stateless @TransactionAttribute public class TrucDaoImpl implements TrucDao { @PersistenceContext(unitName = "trucUnit") protected EntityManager em; public Truc findByName(String name) { Query q = null; Truc res = null; if (name != null) { q = em.createQuery("Select tr From Truc tr Where tr.name = :name"); q.setParameter("name", name); try { res = (Truc) q.getSingleResult(); } catch (NoResultException e) { // log pas de result } } return res; } } 

C'est la méthode findByName() qu'on va vouloir tester unitairement, et sans se fatiguer à recréer toute une base à chaque fois. Vous allez voir, c'est tellement simple que ça en devient marrant.

Vous aurez envie d'ajouter ces dépendances à votre pom.xml, si vous en avez un, sinon, vous aurez envie d'essayer Maven :p
 <!-- Pour faire tourner le machin --> <dependency> <groupId>org.unitils</groupId> <artifactId>unitils</artifactId> <version>1.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.2.3</version> <scope>test</scope> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>5.7</version> <classifier>jdk15</classifier> <scope>provided</scope> </dependency> 
<!-- Il faut y ajouter ça pour que ça passe dans les tests maven --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.5.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.3.2.GA</version> <scope>test</scope> </dependency>
(note : HSQLDB est un base en mémoire en java)

On arrive presque au test ! (oui c'est plus long que prévu, mais il n'y a rien que je déteste plus que de tomber sur un tuto faisant l'impasse sur les fichiers de conf !)

Dernière étape avant de rentrer dans le vif du sujet : unitilis.properties
 database.driverClassName=org.hsqldb.jdbcDriver database.url=jdbc:hsqldb:mem:unitTestDB database.userName=sa database.password= database.schemaNames=PUBLIC database.dialect=hsqldb 
C'est la conf par défaut : on crée une base hsql bidon, et ça roule tout seul.

C'est là que ça devient intéressant (fiou !) : la classe TrucDaoImplTest.java :
 @DataSet @Transactional(TransactionMode.ROLLBACK) @JpaEntityManagerFactory(configFile = "META-INF/persistence-test.xml", persistenceUnit = "trucUnitTest") public class TrucDaoImplTest extends UnitilsTestNG { @Test(groups = "testDAO") public void testFindByName() { Truc truc = null; TrucDao trucDao = new TrucDaoImpl(); JpaUnitils.injectJpaResourcesInto(trucDao); truc = trucDao.findByName("coco"); // asserts et tout et tout } } 
Donc on va voir tout ça en détail :

- JpaUnitils.injectJpaResourcesInto() : c'est la fonction magique, qui va injecter un mock de l'entity manager bindé sur la base hsql qui, elle, sera remplie par dbunit. Sans cette instruction, l'entity manager n'est pas injecté, donc NullPointer.
- @Test : c'est tout ce dont vous avez besoin pour déclarer un test unitaire avec TestNG (simple non ?)
- extends UnitilsTestNG : pour faire le lien entre unitils et testNG
- @JpaEntityManagerFactory : comme son nom l'indique, c'est la factory qui, à partir d'un fichier de conf jpa et d'un nom de persistence unit, va fabriquer notre mock de l'entity manager.
- @Transactional : gérer le mode de transaction (ici, il y a un rollback de la base entre chaque test)

J'ai gardé le meilleur pour la fin : @DataSet. Cette annotation indique à dbunit où trouver sa base de données. Sans paramètres, il va chercher dans le même package un fichier xml du même nom que la classe de test. Il est possible de lui indiquer plusieurs dataSets, afin de réutiliser les données de référence par exemple.

Le DataSet de DBUnit est un fichier xml simple, mais avec une particularité qui fait tout le sel de la manoeuvre : vous n'êtes pas obligé d'y mettre toutes les tables, et au sein d'une même table, vous n'êtes pas obligés de mettre tous les champs.

Pour une entité de 25 attributs, si vous voulez tester un findByName, vous ne mettez que des couples ID, Nom et ça marche tout aussi bien !

Voila notre fichier TrucDaoImplTest.xml :
 <?xml version='1.0' encoding='UTF-8'?> <dataset> <TAB_TRUC ID="12" NAME="toto" /> <TAB_TRUC ID="12" NAME="coco" /> <TAB_TRUC ID="12" NAME="hoho" /> </dataset> 
Voila, le tour est joué. Il reste juste une subtilité : le fichier persistence-test.xml doit contenir la déclaration des entités, sinon il ne les trouvera pas tout seul.


Avouez que c'est la classe non ?


tags : Java EE, JPA, EJB, Tests

Publié dans Java

Commenter cet article