Comment dois-je tester un EJB 3.1 qui obtient une instance de EntityManager injecté?
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 Java EE Developers. J'ai déjà essayé des choses comme ça:
@Before
public void setUp() throws Exception {
ejbContainer = EJBContainer.createEJBContainer();
service = (CommentService) ejbContainer.getContext()
.lookup("Java:global/classes/CommentService");
}
Mais tout ce que j'ai obtenu c'est:
javax.ejb.EJBException:
No EJBContainer provider available: no provider names had been found.
Tout d'abord, assurez-vous de faire la distinction entre tests unitaires et tests d'intégration. JUnit est juste un framework 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é à définir un test unitaire de CommentService.findAll()
. Qu'est-ce que ça veut dire? Cela signifie que je vérifierai qu'en appelant la méthode findAll()
, commentService invoquera la requête nommée nommée par la constante de chaîne FIND_ALL
.
Grâce à l'injection de dépendance et au stubbing, vous pouvez facilement y parvenir en utilisant par exemple Mockito pour éliminer le EntityManager
. Pour le test unitaire, nous nous concentrons uniquement sur la logique métier dans findAll()
, donc je ne prendrai pas la peine de tester la recherche du service Commentaire non plus - tester que le service Commentaire peut être recherché et câblé à une instance de gestionnaire d'entité appropriée fait partie d'un test d'intégration, et non 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 l'implémentation de findAll()
. 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 a été renvoyé à l'appelé.
De plus, le test unitaire ci-dessus vérifie que l'implémentation de 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, sauf si je soupçonne qu'il y a des bogues dans le code tiers, donc supprimer ces dépendances me permet de concentrer le test entièrement sur la logique métier du service Comment.
Cela peut prendre un peu de temps pour s'adapter à l'état d'esprit du comportement de test à l'aide de stubs, mais c'est 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 restreindre la portée de chaque test pour exclure les dépendances externes .
La réponse acceptée nécessite de se moquer de beaucoup de code, y compris la couche de persistance. Utilisez plutôt un conteneur intégré pour tester les beans réels; sinon, se moquer de la couche de persistance entraîne un code qui teste à peine tout ce qui est utile.
Utilisez un bean session avec un gestionnaire d'entités qui 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 bean 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
fichier 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 (comme 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 les fichiers dépendances, session et entity, persistance, test implémentés exactement comme indiqué, le ou les tests doivent réussir. (Les exemples sur Internet sont terriblement inadéquats.)
Pourquoi ne pas utiliser Arquillian pour écrire des tests unitaires pairs et les exécuter dans un vrai conteneur!?
Plus de moqueries. Fini le cycle de vie des conteneurs et les problèmes de déploiement. Juste de vrais tests!
Les simulations peuvent être tactiques, mais le plus souvent, elles sont utilisées pour faire fonctionner le code en dehors d'un environnement réel. Arquillian vous laisse abandonner les simulacres et écrire de vrais tests. En effet, Arquillian apporte votre test à l'exécution, vous donnant accès aux ressources du conteneur, à des commentaires significatifs et à un aperçu du fonctionnement réel du code.
En savoir plus sur Fonctionnalités Arquillian.
Il est possible d'écrire des tests unitaires qui s'exécutent sur un conteneur, mais la mise en garde est que le conteneur/serveur d'applications doit être en place. Comme ce n'est pas vraiment pratique, l'approche générale consiste à utiliser un conteneur "factice" pour exécuter vos tests unitaires. Pour cela, consultez JUnitEE ou ejb3unit: