Tester la persistance Java

Authors: Dan Allen, Bartosz Majsak, Alexis Hassler Translators: Emmanuel Hugonnet, Philippe Charriere Language:
Tags: jpa, database, persistence, transactions Last Update:Sep 21, 2017

Ce guide vous explique comment utiliser Arquillian pour tester la persistance Java (JPA). Après avoir lu ce guide, vous serez capable de :

  • créer une archive de test, contenant un descripteur JPA (persistence.xml),
  • injecter un EntityManager et un UserTransaction dans votre test,
  • enregistrer des entités puis les interroger via JPQL et l’API Criteria de JPA 2,
  • exécuter les tests en utilisant différents fournisseurs JPA.

Vous allez découvrir qu’Arquillian propose la recette idéale pour tester JPA ou simplement expérimenter comment ça fonctionne. Nous avons conçu ce guide de façon à ce qu’il soit facile à suivre pour que vous puissiez vous y référer à chaque fois que vous aurez besoin de revenir sur JPA.

Hypothèses

Nous considérerons que vous avez déjà lu soit le guide Démarrage Rapide, soit le guide Get Started Faster with Forge et que vous avez mis en place une suite de tests Arquillian dans un projet Maven. Nous allons ajouter une entité JPA au projet afin de créer un test élémentaire de persistance Java (JPA). Vous pouvez aussi appliquer ces instructions pour tester vos propres entités.

Les instructions dans ce guide sont spécifiques à un projet Maven, mais rappelez-vous qu’Arquillian n’est en aucune façon lié à Maven. Nous allons exécuter les tests dans un serveur GlassFish embarqué et dans une instance locale de JBoss AS 7, mais vous pouvez utiliser n’importe quel conteneur supportant JPA supporté par Arquillian.

Vous ne pouvez pas utiliser le conteneur arquillian-weld-ee-embedded dans ce tutoriel car Weld ne fournit pas de service JPA (Weld ne fournit que CDI).

Objectif

L’application a une entité Game (jeux vidéo) avec deux champs :

  • id – la clé primaire
  • title – le titre du jeu

Nous allons écrire un test qui enregistre des occurrences dans la base de données puis qui les retrouve en utilisant des requêtes JPQL ou l’API Criteria de JPA 2. Une fois que nous aurons terminé, le test exécutera les trois tâches suivantes :

  • enregistrer des entités dans la base de données en utilisant l’ EntityManager de JPA,
  • interroger la base en utilisant JPQL,
  • interroger la base en utilisant l’API Criteria de JPA 2.

Le code source complet est disponible dans le projet d’exemples Arquillian sur github. Tout ce que vous devrez faire pour le voir en action, c’est d’exécuter la commande ci-dessous (et d’un peu de patience, en attendant que Maven télécharge les dépendances).

mvn test

Structure du Projet

Afin que vous puissiez prendre vos repères, voici la structure de répertoire du projet :

  • src/
    • main/
      • java/
        • org/
          • arquillian/
            • example/
              • Game.java
      • resources/
        • META-INF/
          • persistence.xml
    • test/
      • java/
        • org/
          • arquillian/
            • example/
              • GamePersistenceTest.java
      • resources/
        • arquillian.xml
      • resources-glassfish-embedded/
        • glassfish-resources.xml
        • logging.properties
        • test-persistence.xml
      • resources-jbossas-managed/
        • test-persistence.xml
  • pom.xml

Game est l’entité JPA et test-persistence.xml est une version modifiée de persistence.xml, qui fournit la définition de notre Persistence Unit pour l’environnement de test. Notez que deux répertoires contiennent un fichier test-persistence.xml, un pour chaque conteneur que nous utiliserons. Nous verrons cela plus tard.

Nous recommandons d’utiliser un descripteur JPA dédié aux tests, avec une DataSource de test et une configuration spécifique de votre fournisseur de persistance. Par exemple, en environnement de test, vous pourrez vouloir adopter une stratégie “drop-and-create-tables” pour gérer le schéma de la base de données. Vous pouvez aussi vouloir voir les requêtes à la base de données dans la sortie des traces. Ce paramètrage peut être réalisé dans test-persistence.xml sans affecter l’application principale, comme nous le verrons plus loin. Nous laisserons donc de côté le fichier persistence.xml car c’est la configuration pour l’environnement de production.

Voici le code source de l’entité Game, annotée en @Entity :

src/main/resources/org/arquillian/example/Game.java
package org.arquillian.example;
 
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
@Entity
public class Game implements Serializable {
    private Long id;
    private String title;
 
    public Game() {}
 
    public Game(String title) {
        this.title = title;
    }
 
    @Id @GeneratedValue
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    @NotNull
    @Size(min = 3, max = 50)
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    @Override
    public String toString() {
        return "Game@" + hashCode() + "[id = " + id + "; title = " + title + "]";
    }
}

La clé primaire est définie en utilisant l’annotation @Id sur le champ. Les autres colonnes sont automatiquement déduites des propriétés du bean (avec la convention getter/setter). Vous pouvez utiliser l’annotation @Column pour modifier explicitement le nom de la colonne. Sinon, le nom de la colonne est défini en retirant le préfixe “get” de la méthode de lecture de la propriété du bean et en mettant le premier caractère du reste en minuscule (par exemple, getTitle() → title).

Nous pouvons aussi utiliser les annotations standards de Bean Validation pour imposer des contraintes. Ici, le titre est obligatoire et doit avoir entre 3 et 50 caractères.

Ecrire le Test

Ecrivons un nouveau cas de test avec JUnit 4 et Arquillian, GamePersistenceTest, et préparons-le pour tester nos opérations JPA. Nous exploiterons CDI pour qu’il nous fournisse les ressources dont nous avons besoin via l’injection de dépendance. (Nous aurions aussi pu utiliser un EJB utilitaire pour gérer les transactions, ce que nous envisagerons dans un guide séparé).

src/test/java/org/arquillian/example/GamePersistenceTest.java
package org.arquillian.example;

import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class GamePersistenceTest {
    @Deployment
    public static Archive<?> createDeployment() {
        return ShrinkWrap.create(WebArchive.class, "test.war")
            .addPackage(Game.class.getPackage())
            .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }
 
    private static final String[] GAME_TITLES = {
        "Super Mario Brothers",
        "Mario Kart",
        "F-Zero"
    };
    
    @PersistenceContext
    EntityManager em;
    
    @Inject
    UserTransaction utx;
 
    // tests go here
}

Etudions ce code depuis le début pour comprendre ce qu’il se passe avant de voir les tests.

@RunWith(Arquillian.class)
Indique à JUnit de déléguer l’exécution du test au runner Arquillian. Cela permet à Arquillian d’enrichir votre test avec un modèle de composant, une gestion de cycle de vie par un conteneur et l’injection de dépendance, entre autres. Notez que vous n’avez pas à étendre une classe de base, ce qui vous laisse libre pour d’autres besoins.
Méthode @Deployment
Produit une archive de “micro-déploiement” avec l’API ShrinkWrap. Arquillian déploie cette archive dans le conteneur, incluant la classe de test et quelques classes d’infrastructure. Le test est alors exécuté comme un composant au sein de cette mini-application. Le périmètre de cette archive isole le test du reste du monde.
Constante GAME_TITLES
Données de test
@PersistenceContext EntityManager
Injecte le contexte de persistance (c-à-d. EntityManager) directement dans le test, comme si le test était un managed bean.
@Inject UserTransaction
Injecte une transaction JTA directement dans le test, un service fourni au managed bean par CDI (JSR-299).

Pour ne pas encombrer notre logique de test avec l’initialisation de la persistance, nous allons ajouter des méthodes qui seront exécutées avant et après chaque exécution de test. Jetons un coup d’oeil à ce code.

La méthode @Before, invoquée avant chaque test; aura les responsabilités suivantes :

  1. nettoyer la base de données des résidus des tests précédents,
  2. insérer des données qui seront utilisées par nos tests,
  3. démarrer une transaction.

Voici la méthode que vous allez ajouter à votre test, avec une instruction d’import :

<!-- clip -->
import org.junit.Before;
<!-- clip -->

@Before
public void preparePersistenceTest() throws Exception {
    clearData();
    insertData();
    startTransaction();
}

private void clearData() throws Exception {
    utx.begin();
    em.joinTransaction();
    System.out.println("Dumping old records...");
    em.createQuery("delete from Game").executeUpdate();
    utx.commit();
}

private void insertData() throws Exception {
    utx.begin();
    em.joinTransaction();
    System.out.println("Inserting records...");
    for (String title : GAME_TITLES) {
        Game game = new Game(title);
        em.persist(game);
    }
    utx.commit();
    // clear the persistence context (first-level cache)
    em.clear();
}

private void startTransaction() throws Exception {
    utx.begin();
    em.joinTransaction();
}

Nous aurons aussi besoin d’une méthode qui valide (commit) la transaction après chaque test, ce qui requiert un autre import :

<!-- clip -->
import org.junit.After;
<!-- clip -->

@After
public void commitTransaction() throws Exception {
    utx.commit();
}

Arquillian exécute les méthodes @Before and @After dans le conteneur, respectivement avant et après chaque méthode de test. La méthode @Before est exécutée après que les injections aient été faites.

Notez que nous devons explicitement joindre l’ EntityManager à la transaction JTA. Cette étape est nécessaire car nous utilisons ces deux ressources indépendamment. Cela peut sembler étrange si vous avez l’habitude d’utiliser JPA depuis un EJB, où l’association est faite automatiquement.

Requête JPQL

Vous trouverez ici un test qui vérifie qu’on puisse accéder aux enregistrements en JPQL. Nous avons laissé quelques traces pour mieux suivre ce qui se passe.

<!-- clip -->
import java.util.List;
import org.junit.Test;
<!-- clip -->

@Test
public void shouldFindAllGamesUsingJpqlQuery() throws Exception {
    // given
    String fetchingAllGamesInJpql = "select g from Game g order by g.id";

    // when
    System.out.println("Selecting (using JPQL)...");
    List<Game> games = em.createQuery(fetchingAllGamesInJpql, Game.class).getResultList();

    // then
    System.out.println("Found " + games.size() + " games (using JPQL):");
    assertContainsAllGames(games);
}

Le test se termine par un appel à la méthode assertContainsAllGames. C’est une assertion personnalisée, qui vérifie que la collection en retour contient tous les titres stockés en base.

<!-- clip -->
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.junit.Assert;
<!-- clip -->

private static void assertContainsAllGames(Collection<Game> retrievedGames) {
    Assert.assertEquals(GAME_TITLES.length, retrievedGames.size());
    final Set<String> retrievedGameTitles = new HashSet<String>();
    for (Game game : retrievedGames) {
        System.out.println("* " + game);
        retrievedGameTitles.add(game.getTitle());
    }
    Assert.assertTrue(retrievedGameTitles.containsAll(Arrays.asList(GAME_TITLES)));
}

L’utilisation d’une méthode d’assertion séparée nous procure deux bénéfices :

  • cela exprime clairement ce que nous attendons,
  • elle peut être réutilisée pour d’autres tests.

Voyons maintenant une nouveauté de JPA 2, l’API Criteria.

Génération du méta-modèle JPA 2

Avec l’API Criteria, vous utiliserez les classes du méta-modèle JPA 2 afin de rester “type-safe”. Pour générer ces classes avec Maven, il faut d’abord dire à Maven d’utiliser le JDK 6.

pom.xml
<!-- clip -->
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
    </plugins>
</build>
<!-- clip -->

Nous devons aussi configurer Maven pour qu’il exécute le processeur d’annotation, simplement en ajoutant le générateur de méta-modèle JPA d’Hibernate en tant que dépendance pour la compilation uniquement.

pom.xml
<!-- clip -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <version>1.2.0.Final</version>
    <scope>provided</scope>
</dependency>
<!-- clip -->

Le générateur de méta-modèle sera exécuté automatiquement si vous utilisez le compilateur du JDK 6 et que le processeur d’annotation est dans le classpath.

Avoir la génération du méta-modèle dans Eclipse est un peu plus compliqué. Commencez par créer un fichier nommé .factorypath à la racine du projet et mettez-y la configuration suivante :

.factorypath
<factorypath>
    <factorypathentry kind="VARJAR" enabled="true" runInBatchMode="false"
        id="M2_REPO/org/hibernate/hibernate-jpamodelgen/1.2.0.Final/hibernate-jpamodelgen-1.2.0.Final.jar"/>
    <factorypathentry kind="VARJAR" enabled="true" runInBatchMode="false"
        id="M2_REPO/org/hibernate/javax/persistence/hibernate-jpa-2.0-api/1.0.0.Final/hibernate-jpa-2.0-api-1.0.0.Final.jar"/>
</factorypath>

Puis faites un clic droit sur le projet et sélectionnez Properties. Dépliez le noeud Java Compiler dans l’arbre des paramètres et sélectionnez Annotation Processing. Enfin, changez les valeurs suivantes :

  • cochez “Enable project specific settings”,
  • cochez “Enable annotation processing”,
  • renseignez “Generated source directory” avec “target/generated-sources/annotations” (sans les quotes),
  • cliquez sur le bouton Apply et acceptez un build complet,
  • décochez “Enable annotation processing”,
  • cliquez sur le bouton Apply, sans le build complet,
  • cochez “Enable annotation processing”,
  • cliquez sur le bouton Apply et acceptez un build complet.

Vous devriez voir le fichier Game_.java dans le répertoire target/generated-sources/annotations , qui devrait être dans le build path.

Oui, vous devez bidouiller un peu, tout comme vous devez mettre un coup de latte au distributeur automatique de friandises pour qu’il laisse tomber votre friandise ~:) Si ça vous ennuie, vous pouvez passer l’étape de génération du méta-modèle et référencer simplement les colonnes avec des chaînes de caractères.

Finalement, vous voilà prêt à écrire la requête Criteria.

Requête avec l’API Critiera

Voici une copie du test précédent qui a été modifié pour utiliser l’API Criteria. Notez que ce test dépend de la génération de la classe de méta-modèle Game_ pendant l’étape de compilation.

<!-- clip -->
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
<!-- clip -->

@Test
public void shouldFindAllGamesUsingCriteriaApi() throws Exception {
    // given
    CriteriaBuilder builder = em.getCriteriaBuilder();
    CriteriaQuery<Game> criteria = builder.createQuery(Game.class);
    		
    Root<Game> game = criteria.from(Game.class);
    criteria.select(game);
    // TIP: If you don't want to use the JPA 2 Metamodel,
    // you can change the get() method call to get("id")
    criteria.orderBy(builder.asc(game.get(Game_.id)));
    // No WHERE clause, which implies select all

    // when
    System.out.println("Selecting (using Criteria)...");
    List<Game> games = em.createQuery(criteria).getResultList();

    // then
    System.out.println("Found " + games.size() + " games (using Criteria):");
    assertContainsAllGames(games);
}

Pour que JPA fonctionne, il a besoin d’une Persistence Unit.

Nous définissons la Persistence Unit dans un fichier test-persistence.xml correspondant au conteneur cible. Shrinkwrap récupère ce fichier depuis le classpath et le copie à la position standard dans l’archive.

.addAsResource("test-persistence.xml", "META-INF/persistence.xml")

Voici la structure de l’archive que Shrinkwrap va générer pour notre cas de test (sans l’infrastructure Arquillian) :

  • WEB-INF/
    • beans.xml
    • classes/
      • META-INF/
        • persistence.xml
      • org/
        • arquillian/
          • example/
            • Game.class
            • GamePersistenceTestCase.class
            • Game_.class
    • lib/
      • *.jar

Jetons un coup d’oeil au descripteur du Persistence Unit que nous utiliserons pour le test, en commençant par celui pour Glassfish embarqué.

Configuration pour GlassFish

Voici le descripteur que nous utiliserons pour Glassfish embarqué.

src/test/resources-glassfish-embedded/test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <jta-data-source>jdbc/arquillian</jta-data-source>
        <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
            <property name="eclipselink.logging.level.sql" value="FINE"/>
            <property name="eclipselink.logging.parameters" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Nous renseignons deux propriétés spécifiques pour activer des fonctionnalités propre sexà EclipseLink, le fournisseur intégré.

eclipselink.ddl-generation
Configure la commande de génération du schéma de la base ; la valeur drop-and-create-tables indique à EclipseLink de générer la base de données conformément aux entités JPA, à chaque exécution.
eclipselink.logging.level.sql
Configure la journalisation des requêtes ; la valeur FINE active les traces des requêtes SQL, nous permettant de suivre l’activité sur la base de données.

Les traces d’EclipseLink devront aussi être configurées dans le fichier de configuration des logs standards de Java, en y activant le niveau FINE.

src/test/resources-glassfish-embedded/logging.properties
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%4$s: %5$s%n
java.util.logging.ConsoleHandler.level=FINEST

L’unité de persistance dans test-persistence.xml fait référence à une DataSource nommée jdbc/arquillian. Où est-elle définie ? Eh bien, c’est quelque chose que le conteneur Arquillian doit configurer.

Nous voulons utiliser l’API de Glassfish pour créer un pool de connexions JDBC et les ressources associées. Mais nous ne voulons pas le coder. Nous voulons juste le déclarer. C’est là qu’Arquillian intervient.

D’abord, nous créons un fichier glassfish-resources.xml contenant la définition des ressources, que Glassfish sait consommer.

src/test/resources-glassfish-embedded/glassfish-resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC
    "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
    "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-resource pool-name="ArquillianEmbeddedDerbyPool"
        jndi-name="jdbc/arquillian"/>
    <jdbc-connection-pool name="ArquillianEmbeddedDerbyPool"
        res-type="javax.sql.DataSource"
        datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource"
        is-isolation-level-guaranteed="false">
        <property name="databaseName" value="target/databases/derby"/>
        <property name="createDatabase" value="create"/>
    </jdbc-connection-pool>
</resources>

Nous avons ainsi isolé la définition de la DataSource du reste du test de la même façon dont nous avons procédé pour l’application principale. L’intérêt supplémentaire est que nous pouvons définir n’importe quelle ressource dont nous aurions besoin pour notre test. Je vous laisse imaginer les possibilités.

A présent, nous devons dire à Arquillian d’utiliser ce fichier. Nous ouvrons la configuration Arquillian et configurons l’adaptateur du conteneur Glassfish embarqué pour qu’il prenne ce fichier, avec lequel il alimentera la commande add-resources de l’API d’administration de Glassfish.

src/test/resources/arquillian.xml
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
    <container qualifier="glassfish-embedded" default="true">
        <configuration>
            <property name="resourcesXml">
                src/test/resources-glassfish-embedded/glassfish-resources.xml
            </property>
        </configuration>
    </container>
</arquillian>

A la place de cela, nous pourrions sauter l’étape de la configuration de la DataSource et simplement inclure les informations de connexion à la base de données directement dans test-persistence.xml, en utilisant les propriétés standard de connexion à la base :

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <properties>
            <property name="javax.persistence.jdbc.driver"
                value="org.apache.derby.jdbc.EmbeddedDriver"/>
            <property name="javax.persistence.jdbc.url"
                value="jdbc:derby:target/databases/derby;create=true"/>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
            <property name="eclipselink.logging.level.sql" value="FINE"/>
            <property name="eclipselink.logging.parameters" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

N’oubliez pas toutefois que le basculement d’une DataSource JNDI vers une connexion explicite modifie l’architecture entre l’environnement de production et celui de test, et ainsi réduit la confiance à accorder aux tests pour identifier toute défaillance potentielle.

Tout ce qu’il nous reste à faire, c’est de configurer l’adaptateur de conteneur et d’exécuter le test.

Préparer le Test pour GlassFish

Nous allons séparer les conteneurs cibles en utilisant des profils Maven. Tous les profils vont partager un ensemble de dépendances (comme dans le guide de Démarrage Rapide) :

pom.xml
<!-- clip -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.0.0.Final</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.arquillian.junit</groupId>
        <artifactId>arquillian-junit-container</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<!-- clip -->

Si vous prévoyez d’utiliser une base de données qui n’est pas incluse dans le serveur d’applications, comme MySQL, vous devrez inclure ses librairies clientes dans le classpath. Vous pouvez consulter le tutoriel pour avoir un exemple d’utilisation de H2 à la place de Derby.

A présent, ajoutons (ou modifions) le profil pour Glassfish embarqué.

pom.xml
<!-- clip -->
<profile>
    <id>arquillian-glassfish-embedded</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
            <version>1.0.0.CR3</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.main.extras</groupId>
            <artifactId>glassfish-embedded-web</artifactId>
            <version>3.1.2</version>
        </dependency>
    </dependencies>
    <build>
        <testResources>
            <testResource>
                <directory>src/test/resources</directory>
            </testResource>
            <testResource>
                <directory>src/test/resources-glassfish-embedded</directory>
            </testResource>
        </testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12</version>
                <configuration>
                    <systemPropertyVariables>
                        <java.util.logging.config.file>
                            ${project.build.testOutputDirectory}/logging.properties
                        </java.util.logging.config.file>
                        <derby.stream.error.file>
                            ${project.build.directory}/derby.log
                        </derby.stream.error.file>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile>
<!-- clip -->

Nous ajoutons explicitement le répertoire src/test/resources-glassfish-embedded comme une ressource de test afin que le fichier test-persistence.xml soit placé dans le classpath. Nous avons aussi configuré le plugin surefire pour qu’il passe le fichier de configuration des traces Java au processus en fork afin que le logging SQL fonctionne. Enfin, nous envoyons le fichier le log de Derby dans le répertoire cible du build afin qu’il soit supprimé lorsqu’on nettoie le projet.

Si vous n’avez pas prévu de tester dans différents conteneurs, vous n’avez pas besoin de mettre la configuration ci-dessus dans un profil.

Exécuter le Test dans Glassfish

Quand vous avez fini de tout configurer, vous pouvez exécuter le test dans l’IDE en sélectionnant Run As > JUnit Test ou depuis Maven avec la commande suivante :

$ mvn clean test

Le profil pour Glassfish embarqué est activé par défaut (de même que l’adaptateur de conteneur dans arquillian.xml). Vous trouverez des extraits de la sortie du test ci-dessous.

...
Running org.arquillian.example.GamePersistenceTest
...
INFO: GlassFish Server Open Source Edition 3.1.2 (java_re-private) ...
...
INFO: command add-resources result: PlainTextActionReporterSUCCESSDescription: add-resources AdminCommandnull
    JDBC connection pool ArquillianEmbeddedDerbyPool created successfully.
    JDBC resource jdbc/arquillian created successfully.
...
INFO: WEB0671: Loading application [test] at [/test]
...
Dumping old records...
FINE: DELETE FROM GAME
Inserting records...
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
   bind => [50, SEQ_GEN]
FINE: SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
   bind => [SEQ_GEN]
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [3, F-Zero]
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [1, Super Mario Brothers]
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [2, Mario Kart]
Selecting (using JPQL)...
FINE: SELECT ID, TITLE FROM GAME ORDER BY ID ASC
Found 3 games (using JPQL):
* Game@599290122[id = 1; title = Super Mario Brothers]
* Game@1550721071[id = 2; title = Mario Kart]
* Game@1107500305[id = 3; title = F-Zero]
FINE: DELETE FROM GAME
Inserting records...
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [5, Mario Kart]
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [6, F-Zero]
FINE: INSERT INTO GAME (ID, TITLE) VALUES (?, ?)
   bind => [4, Super Mario Brothers]
Selecting (using Criteria)...
FINE: SELECT ID, TITLE FROM GAME ORDER BY ID ASC
Found 3 games (using Criteria):
* Game@1020493092[id = 4; title = Super Mario Brothers]
* Game@1622992302[id = 5; title = Mario Kart]
* Game@294335520[id = 6; title = F-Zero]
...

Félicitation ! Green bar! Ça c’est du test d’intégration !

Lorsque vous introduisez des fonctions de mapping avancées, comme le chargement tardif (ou lazy loading) et les groupes de fetch, vous risquez de tomber sur des erreurs et des alertes causées par Glassfish embarqué et le mode de fonctionnement d’EclipseLink. Des éléments de configuration supplémentaires sont nécessaires pour contourner ce problème. Vous pouvez consulter le blog de Markus Eisele pour les instructions. Vous ne rencontrerez pas ce problème si vous utilisez un adaptateur de conteneur managed ou remote.

Exécuter le Test dans JBoss AS 7

Nous pouvons exécuter exactement le même test dans JBoss AS 7 en faisant quelques petites adaptations du classpath.

Nous aurons besoin d’un Persistence Unit différent, qui utilise une DataSource disponible dans JBoss AS et qui positionne quelques paramètres Hibernate.

Si nous étions dans JBoss AS 7.0, nous aurions besoin de pouvoir ajouter manuellement une DataSource dans la configuration de JBoss AS ou d’utiliser la DataSource intégrée, java:jboss/datasources/ExampleDS. Voici le descripteur de la Persistence Unit pour JBoss AS 7.0 qui utilise la DataSource intégrée.

src/test/resources-jbossas-managed/test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Les propriétés spécifiques hibernate.hbm2ddl.auto et hibernate.show_sql ont les mêmes fonctions que les propriétés EclipseLink décrites précédemment.

Si vous utilisez JBoss AS 7.1, comme nous le recommandons, vous pouvez déclarer dynamiquement une nouvelle DataSource en ajoutant un descripteur (i.e. un fichier avec l’extension -ds.xml), contenant une ou plusieurs définitions de DataSources, au répertoire META-INF d’une archive Java ou au répertoire WEB-INF d’une archive Web.

Voici un déscripteur qui définit une DataSource H2 avec le nom JNDI jdbc/arquillian (le même nom JNDI que la DataSource que nous avons définie précédemment pour Glassfish):

src/test/resources/jbossas-ds.xml
<?xml version="1.0" encoding="UTF-8"?>
<datasources xmlns="http://www.jboss.org/ironjacamar/schema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.jboss.org/ironjacamar/schema
        http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd">
    <datasource enabled="true"
        jndi-name="jdbc/arquillian"
        pool-name="ArquillianEmbeddedH2Pool">
        <connection-url>jdbc:h2:mem:arquillian;DB_CLOSE_DELAY=-1</connection-url>
        <driver>h2</driver>
    </datasource>
</datasources>

JBoss AS 7.1 supporte nativement la base de données H2. Pour utiliser une autre base de données, vous devez ajouter le driver correspondant à l’installation, tel que c’est décrit dans le chapitre sur les DataSource du guide de référence JBoss AS 7.1.

Nous devons mettre à jour notre Persistence Unit pour référencer cette nouvelle DataSource :

src/test/resources-jbossas-managed/test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <jta-data-source>jdbc/arquillian</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Nous devons aussi ajouter le descripteur au répertoire WEB-INF de l’archive de test. Ajoutez cet appel de méthode au builder ShrinkWrap dans la méthode @Deployment du cas de test :

.addAsWebInfResource("jbossas-ds.xml")

Inclure ce fichier dans l’archive n’a pas d’impact sur la capacité à exécuter le test dans Glassfish embarqué. La DataSource et la Persistence Unit sont prêtes à l’emploi.

Maintenant nous avons besoin d’un profil Maven qui ajoute le conteneur JBoss AS, ainsi que les ressources JBoss AS dans le classpath :

pom.xml
<!-- clip -->
<profile>
    <id>arquillian-jbossas-managed</id>
    <dependencies>
        <dependency>
            <groupId>org.jboss.as</groupId>
            <artifactId>jboss-as-arquillian-container-managed</artifactId>
            <version>7.1.1.Final</version>
            <scope>test</scope>
        </dependency>
         <dependency>
             <groupId>org.jboss.spec</groupId>
             <artifactId>jboss-javaee-web-6.0</artifactId>
             <version>3.0.0.Final</version>
             <type>pom</type>
             <scope>provided</scope>
             <exclusions>
                 <exclusion>
                     <groupId>xalan</groupId>
                     <artifactId>xalan</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
    </dependencies>
    <build>
        <testResources>
            <testResource>
                <directory>src/test/resources</directory>
            </testResource>
            <testResource>
                <directory>src/test/resources-jbossas-managed</directory>
            </testResource>
        </testResources>
    </build>
</profile>
<!-- clip -->

Maintenant, nous exécutons à nouveau le test avec Maven, mais cette fois-ci nous activons le profil JBoss AS managed.

$ mvn clean test -Parquillian-jbossas-managed

Assurez-vous que la variable d’environnement JBOSS_HOME pointe vers une installation JBoss AS 7.1.1.Final. Sinon, vous pouvez définir la localisation en utilisant la propriété jbossHome dans arquillian.xml.

Vous pouvez exécuter ce test depuis votre IDE ! Importez juste votre projet, activez le profil Maven arquillian-jbossas-managed (et désactivez arquillian-glassfish-embedded), ouvrez le cas de test et sélectionnez “Run As > JUnit Test”. Voila ! Ça marche comme n’importe quel autre test unitaire. Green bar!

Appréciez la recette idéale pour tester JPA !

Même si la préparation peut sembler longue, reconnaissez que nous avons fait le tour de la question. Pour vous en remémorer les avantages, regardez juste la simplicité du cas de test, et rappelez-vous qu’il n’est lié à aucun conteneur Java EE 6 ou implémentation JPA 2 spécifiques.

Share the Knowledge

Find this guide useful?

There’s a lot more about Arquillian to cover. If you’re ready to learn more, check out the other available guides.

Feedback

Find a bug in the guide? Something missing? You can fix it by forking this website, making the correction and sending a pull request. If you’re just plain stuck, feel free to ask a question in the user discussion forum.

Recent Changelog

  • Sep 21, 2017: Fix(scripts) timeout for waiting for timestamp available on pages is by Matous Jobanek

See full history »