EntityManagerのインスタンスをインジェクトするEJB 3.1をテストするにはどうすればよいですか?
考えられるEJB:
@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();
}
}
可能なテスト:
@Test
public void testFindAll() {
List<Comment> all = service.findAll();
Assert.assertEquals(8, all.size());
}
Java EE開発者のためにGlassFish 3.1とEclipse Indigoのみを使用しています。
@Before
public void setUp() throws Exception {
ejbContainer = EJBContainer.createEJBContainer();
service = (CommentService) ejbContainer.getContext()
.lookup("Java:global/classes/CommentService");
}
しかし、私が得たのは:
javax.ejb.EJBException:
No EJBContainer provider available: no provider names had been found.
まず、単体テストと統合テストを区別してください。 JUnitは、テストの整理と実行に役立つフレームワークにすぎませんが、テストの範囲を決定する必要があります。
CommentService.findAll()
のunitテストの定義に興味があると思います。どういう意味ですか?つまり、findAll()
メソッドを呼び出すと、CommentServiceが_FIND_ALL
_文字列定数で指定された名前付きクエリを呼び出すことを確認します。
依存性の注入とスタブのおかげで、例えば MockitoEntityManager
をスタブします。単体テストでは、findAll()
のビジネスロジックにのみ焦点を当てているため、Commentサービスのルックアップのテストも行いません。Commentサービスが検索可能であり、接続されていることをテストします。適切なエンティティマネージャインスタンスへの統合は、単体テストではなく統合テストの範囲内です。
_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!
}
}
_
上記の単体テストでは、findAll()
実装のbehaviorをテストしました。単体テストでは、正しい名前付きクエリが取得され、名前付きクエリによって返された結果が呼び出し先に返されたことを確認しました。
さらに、上記の単体テストでは、findAll()
の実装が、基礎となるJPAプロバイダーと基礎となるデータに関係なく正しいことを検証します。サードパーティのコードにバグがあると疑わない限り、JPAとJPAプロバイダーをテストしたくないので、これらの依存関係を取り除くことで、コメントサービスのビジネスロジックに完全にテストを集中させることができます。
スタブを使用したテスト動作の考え方に調整するには少し時間がかかりますが、EJB 3.1 Beanのビジネスロジックをテストするための非常に強力な手法です。これは、各テストの範囲を分離および絞り込んで外部依存関係を除外できるためです。 。
受け入れられた答えには、永続化レイヤーを含む多くのコードのモックが必要です。代わりに埋め込みコンテナを使用して、実際のBeanをテストします。そうでない場合、永続層をモックすると、有用なものをほとんどテストしないコードになります。
永続性ユニットを参照するエンティティマネージャでセッションBeanを使用します。
@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;
}
}
エンティティBean:
@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;
}
}
この永続性ユニットは、persistence.xml
ファイルは次のとおりです。
<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>
トランザクションタイプはJTA
でなければなりません。
次に、EJBコンテナー(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());
}
}
次に、2つの依存関係(Maven POMなど)を追加します。
<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>
依存関係、セッションおよびエンティティ Bean、永続ファイル、テストファイルが実装されている示されているとおりに、テストに合格する必要があります。 (インターネット上の例は、ひどく不十分です。)
Arquillian を使用して、ユニットテストでさえ実際のコンテナで実行しないのはなぜですか?
モックはもうありません。コンテナのライフサイクルと展開の面倒はもうありません。まさに本物のテストです!
モックは戦術的な場合もありますが、多くの場合、実際の環境外でコードを機能させるために使用されます。 Arquillianでは、モックを捨てて実際のテストを作成できます。これは、Arquillianがテストをランタイムにもたらし、コンテナーリソースへのアクセス、意味のあるフィードバック、およびコードの実際の動作に関する洞察を提供するためです。