47 votes

Tester un EJB avec JUnit

Comment tester un EJB 3.1 qui reçoit une instance d'EntityManager injectée ?

Un EJB possible :

@Stateless
@LocalBean
public class CommentService {

    @PersistenceContext
    private EntityManager em;

    public List<Comment> findAll() {
        TypedQuery<Comment> query = em.createNamedQuery(
            Comment.FIND_ALL, Comment.class
        );
        return query.getResultList();
    }

}

Un test possible :

@Test
public void testFindAll() {
    List<Comment> all = service.findAll();
    Assert.assertEquals(8, all.size());
}

J'utilise uniquement GlassFish 3.1 et Eclipse Indigo pour les développeurs Java EE. J'ai déjà essayé ce genre de choses :

@Before
public void setUp() throws Exception {
    ejbContainer = EJBContainer.createEJBContainer();
    service = (CommentService) ejbContainer.getContext()
        .lookup("java:global/classes/CommentService");
}

Mais tout ce que j'ai eu, c'est :

javax.ejb.EJBException:
No EJBContainer provider available: no provider names had been found.

89voto

Oliver Watkins Points 1624

La réponse acceptée nécessite la mise en place d'un grand nombre de codes, y compris la couche de persistance. Utilisez un conteneur intégré pour tester les haricots réels, au lieu de cela ; sinon, la simulation de la couche de persistance donne un code qui ne teste presque rien d'utile.

Utilisez un haricot de session avec un gestionnaire d'entités qui fait référence à une unité de persistance :

@Stateless
public class CommentService {

    @PersistenceContext(unitName = "pu")
    private EntityManager em;

    public void create(Comment t) {
        em.merge(t);
    }

    public Collection<Comment> getAll() {
        Query q = em.createNamedQuery("Comment.findAll");
        Collection<Comment> entities = q.getResultList();
        return entities;
    }
}

Le haricot d'entité :

@Entity
@NamedQueries({@NamedQuery(name = "Comment.findAll", query = "select e from Comment e")})
public class Comment implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Cette unité de persistance est définie dans le persistence.xml comme suit :

<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

  <persistence-unit name="pu" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>org.glassfish.embedded.tempconverter.Comment</class>
    <properties>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

Le type de transaction doit être JTA .

Ensuite, écrivez un test qui crée et détruit le conteneur EJB (conteneur intégré GlassFish) :

public class CommentTest extends TestCase {

     private Context  ctx;
     private EJBContainer ejbContainer;

    @BeforeClass
    public  void setUp() {
        ejbContainer = EJBContainer.createEJBContainer();
        System.out.println("Opening the container" );
        ctx = ejbContainer.getContext();
    }

    @AfterClass
    public  void tearDown() {
        ejbContainer.close();
        System.out.println("Closing the container" );
    }

    public void testApp() throws NamingException {

        CommentService converter = (CommentService) ctx.lookup("java:global/classes/CommentService");
        assertNotNull(converter);

        Comment t = new Comment();
        converter.create(t);
        t = new Comment();
        converter.create(t);
        t = new Comment();
        converter.create(t);
        t = new Comment();
        converter.create(t);

        Collection<Comment> ts = converter.getAll();

        assertEquals(4, ts.size());
    }
}

Ensuite, ajoutez deux dépendances (par exemple dans un POM Maven) :

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.8.2</version>
    <scope>test</scope>
    <type>jar</type>
</dependency>
<dependency>
    <groupId>org.glassfish.main.extras</groupId>
    <artifactId>glassfish-embedded-all</artifactId>
    <version>3.1.2</version>
    <scope>compile</scope>
</dependency>

Avoir le Dépendances , session y entité haricot, persistance fichier, test mis en œuvre exactement comme indiqué, alors le(s) test(s) devrait(ent) réussir. (Les exemples sur Internet sont terriblement inadéquats).

49voto

Kim Burgaard Points 2178

Tout d'abord, assurez-vous de faire la distinction entre tests unitaires y tests d'intégration . JUnit est juste un cadre qui vous aide à organiser et à exécuter les tests, mais vous devez déterminer la portée de vos tests.

Je suppose que vous êtes intéressé par la définition d'un unité test de CommentService.findAll() . Qu'est-ce que ça veut dire ? Cela signifie que je vais vérifier que l'appel de la findAll() entraîne l'invocation par CommentService de la requête nommée par la méthode FIND_ALL constante de la chaîne.

Grâce à l'injection de dépendances et au stubbing, vous pouvez facilement atteindre cet objectif en utilisant, par exemple, la méthode de l'injection de dépendances. Mockito pour mettre fin à la EntityManager . Pour le test unitaire, nous nous concentrons uniquement sur la logique métier dans findAll() Je ne prendrai donc pas la peine de tester la recherche du service Commentaire non plus. Tester que le service Commentaire peut être recherché et qu'il est relié à une instance appropriée du gestionnaire d'entités relève d'un test d'intégration, pas d'un test unitaire.

public class MyCommentServiceUnitTest {
    CommentService commentService;
    EntityManager entityManager;

    @Before
    public void setUp() {
        commentService = new CommentService();

        entityManager = mock(EntityManager.class);
        commentService.setEm(entityManager); // inject our stubbed entity manager
    }

    @Test
    public void testFindAll() {
        // stub the entity manager to return a meaningful result when somebody asks
        // for the FIND_ALL named query
        Query query = mock(Query.class);
        when(entityManager.createNamedQuery(Comment.FIND_ALL, Comment.class)).thenReturn(query);
        // stub the query returned above to return a meaningful result when somebody
        // asks for the result list
        List<Comment> dummyResult = new LinkedList<Comment>();
        when(query.getResultList()).thenReturn(dummyResult);

        // let's call findAll() and see what it does
        List<Comment> result = commentService.findAll();

        // did it request the named query?
        verify(entityManager).createNamedQuery(Comment.FIND_ALL, Comment.class);
        // did it ask for the result list of the named query?
        verify(query).getResultList();
        // did it return the result list of the named query?
        assertSame(dummyResult, result);

        // success, it did all of the above!
    }
}

Avec le test unitaire ci-dessus, j'ai testé le comportement de la findAll() mise en œuvre. Le test unitaire a vérifié que la requête nommée correcte est obtenue et que le résultat renvoyé par la requête nommée est renvoyé à l'appelé.

De plus, le test unitaire ci-dessus vérifie que l'implémentation de la fonction findAll() est correcte indépendamment du fournisseur JPA sous-jacent et des données sous-jacentes. Je ne veux pas tester JPA et le fournisseur JPA à moins que je ne soupçonne l'existence de bogues dans le code tiers. Le fait d'ignorer ces dépendances me permet de concentrer entièrement le test sur la logique commerciale du service Commentaire.

Il faut parfois un peu de temps pour se faire à l'idée de tester le comportement à l'aide de stubs, mais il s'agit d'une technique très puissante pour tester la logique métier de vos beans EJB 3.1, car elle vous permet d'isoler et de réduire la portée de chaque test afin d'exclure les dépendances externes.

8voto

Heidarzadeh Points 2152

Pourquoi ne pas utiliser Arquillian pour écrire même des tests unitaires et les exécuter dans un vrai conteneur ?

Plus de moqueries. Plus de problèmes liés au cycle de vie des conteneurs et au déploiement. Juste de vrais tests !

Les Mocks peuvent être tactiques, mais le plus souvent, ils sont utilisés pour faire fonctionner du code en dehors d'un environnement réel. Arquillian vous permet d'abandonner les mocks et d'écrire de vrais tests. C'est parce qu'Arquillian apporte votre test au runtime, vous donnant accès aux ressources du conteneur, à un feedback significatif et à un aperçu de la façon dont le code fonctionne réellement.

En savoir plus Caractéristiques arquilliennes.

1voto

Jonathan Points 875

Il est possible d'écrire des tests unitaires qui s'exécutent dans un conteneur, mais le problème est que le conteneur/serveur d'applications doit être opérationnel. Comme ce n'est pas vraiment pratique, l'approche générale est d'utiliser un conteneur "mock" pour exécuter vos tests unitaires. Pour cela, consultez JUnitEE ou ejb3unit :

junitee

ejb3unit

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X