用JUnittesting一个EJB

我应该如何testing一个获得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(); } } 

一个可能的testing:

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

我只使用GlassFish 3.1和Eclipse Indigo for Java EE开发人员。 我已经尝试过这样的事情:

 @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. 

首先,确保你区分unit testing集成testing 。 JUnit只是一个帮助你组织和运行testing的框架,但你必须确定testing的范围。

我假设你有兴趣定义一个CommentService.findAll()单元testing。 那是什么意思? 这意味着我将validation调用findAll()方法导致CommentService调用由FIND_ALLstring常量命名的命名查询。

感谢dependency injection和存根,你可以很容易地实现使用例如Mockito来存储EntityManager 。 对于unit testing,我们只关注findAll()的业务逻辑,所以我不打扰testingComment服务的查询 – testingComment服务可以被查找并连接到适当的实体pipe理器实例处于集成testing的范围内,而不是unit testing。

 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! } } 

通过上面的unit testing,我testing了findAll()实现的行为 。 unit testingvalidation是否获得了正确的命名查询,并将命名查询返回的结果返回给被调用者。

更重要的是,上面的unit testingvalidationfindAll()的实现是正确的, findAll()依赖于底层的JPA提供者和底层的数据。 我不想testingJPA和JPA提供程序,除非我怀疑第三方代码中有错误,所以将这些依赖关系排除在外可让我将testing完全集中在Comment服务的业务逻辑上。

使用存根调整testing行为的思路可能需要一点时间,但是它是testingEJB 3.1 bean的业务逻辑的一种非常强大的技术,因为它可以让您隔离和缩小每个testing的范围,以排除外部依赖关系。

我不同意这个问题的接受答案。 上面接受的答案涉及嘲笑包括持久层的许多代码。 你应该使用一个embedded的容器来testing实际的bean,否则嘲笑持久层会给你一个testing几乎没有用的testing。

原来的海报应该有一个这样的会话bean与一个实体pipe理器指向一个持久性单元:

 @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> 

请注意,交易types需要是JTA。

你所要做的就是编写一个像这样的testing,创build和销毁EJB容器(glassfish embedded container):

 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()); } } 

你只需要将这两个添加到你的依赖。 下面是我的依赖在我的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, 持久性文件, testing文件完全像我这里实现那么你的testing应该通过。 我花了整整两天的时间才弄明白(互联网上的例子是可悲的)。

为什么不使用Arquillian甚至写unit testing,并在真正的容器中运行它们?

没有更多的嘲笑。 没有更多的容器生命周期和部署的麻烦。 只是真正的考验!

嘲笑可以是战术性的,但更多的时候,它们被用来使代码在真实环境之外工作。 Arquillian让你嘲笑嘲笑,写出真正的考验。 这是因为Arquillian将您的testing带入了运行时,让您能够访问容器资源,有意义的反馈和关于代码真正工作的洞察。

更多关于Arquillianfunction。

可以编写针对容器运行的unit testing,但要注意的是容器/应用程序服务器必须启动。 由于这不是很实际,一般的方法是使用“模拟”容器来运行你的unit testing。 为此,请查看JUnitEE或ejb3unit:

junitee

ejb3unit