使用JUnit测试EJB

Mat*_*ias 39 junit jpa java-ee java-ee-6

我应该如何测试获取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();
    }

}
Run Code Online (Sandbox Code Playgroud)

可能的测试:

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

我只使用GlassFish 3.1和Eclipse Indigo for Java EE Developers.我已经尝试过这样的事情:

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

但我得到的只是:

javax.ejb.EJBException:
No EJBContainer provider available: no provider names had been found.
Run Code Online (Sandbox Code Playgroud)

Oli*_*ins 78

接受的答案需要模拟很多代码,包括持久层.使用嵌入式容器来测试实际的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

实体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;
    }
}
Run Code Online (Sandbox Code Playgroud)

此持久性单元在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>
Run Code Online (Sandbox Code Playgroud)

交易类型必须是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());
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,添加两个依赖项(例如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>
Run Code Online (Sandbox Code Playgroud)

具有依赖性,会话实体 bean的,持久性的文件,测试执行文件完全一样所示,那么测试(S)应该通过.(互联网上的例子非常不合适.)

  • 100%确认!此外,您还可以通过更改maven依赖项来切换嵌入式EJB容器.我倾向于使用OpenEJB,因为它在测试期间启动速度更快,你甚至可以考虑在正常构建期间运行这种测试,因为它们不需要花费很多时间.请参阅此处的一些示例:https://tomee.apache.org/examples-trunk/ (6认同)
  • 接受的答案正确地指出了单元测试和集成测试之间的区别.单元测试只能进行很少的测试.它们应该是一些非常具体的破损的迹象.在没有模拟依赖关系的情况下测试容器中的bean意味着引入大量关于被测试类失败的误报的可能性.这不是你想要的_unit test_.您的测试测试您的系统是否正常工作 如果发生故障,您需要开始挖掘,而不是精确指向非常特定的错误. (4认同)
  • Definitley这个答案超过了公认的答案.尽管接受的可能是字面上正确的(单元与集成测试),重要的是:我的代码是否按照我的预期执行.要知道这一点,你必须通过一些实际数据来测试它.模拟很好,很好,很容易,但它永远不会告诉你,你构建了一个错误的复杂标准.这将. (3认同)

Kim*_*ard 43

首先,确保区分单元测试集成测试.JUnit只是一个框架,可以帮助您组织和运行测试,但您必须确定测试的范围.

我假设你有兴趣定义一个单元测试CommentService.findAll().那是什么意思?这意味着我将验证调用该findAll()方法会导致CommentService调用由FIND_ALL字符串常量命名的命名查询.

由于依赖注入和存根,您可以轻松地使用例如Mockito来实现这一点EntityManager.对于单元测试,我们只关注业务逻辑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!
    }
}
Run Code Online (Sandbox Code Playgroud)

通过上面的单元测试,我测试了实现的行为findAll().单元测试验证是否获得了正确的命名查询,并且命名查询返回的结果被返回给被调用者.

更重要的是,上面的单元测试验证了findAll()独立于底层JPA提供程序和底层数据的实现是否正确.我不想测试JPA和JPA提供程序,除非我怀疑第三方代码中存在错误,因此删除这些依赖关系让我将测试完全集中在Comment服务的业务逻辑上.

使用存根调整测试行为的思维方式可能需要一段时间,但它是一种非常强大的技术,用于测试EJB 3.1 bean的业务逻辑,因为它可以让您隔离和缩小每个测试的范围以排除外部依赖性.

  • i + 1但是它会强制你创建一个setter方法(setEm).对我来说没关系,因为为了完全可测试,代码应该考虑到可测试性.您也了解单元测试和集成测试之间的区别.这正是"EJB"的单元测试意味着什么 (2认同)
  • 我不明白这是如何测试他已经在他的对象列表中插入了8个元素.这个测试不够深入. (2认同)

Hei*_*deh 9

为什么不使用Arquillian编写甚至单元测试并在真正的容器中运行它们?

没有更多的嘲笑.不再有容器生命周期和部署麻烦.只是真正的测试!

模拟可以是战术性的,但通常用于使代码在真实环境之外工作.Arquillian让你放弃嘲笑并编写真正的测试.这是因为Arquillian将您的测试带到运行时,使您可以访问容器资源,有意义的反馈以及有关代码如何工作的洞察力.

有关Arquillian功能的更多信息.

  • Arquillian根本不是用户友好的,我花了好几个小时试图运行测试b/c它不是那么明显,或者很容易忘记将类/包/库添加到Shrinkwrap Archive.或许我应该说Shrinkwrap文档编写得不好.文档就是一切,特别是Java,当有这么多的选择和术语时,对于那些刚刚接受并决定他们想要尝试Arquillian的人来说,这是非常压倒性的,但却发现很难找到工作. (9认同)
  • 对我来说,Arquilian还没有达到它所带来的好处所节省的时间比它所需的设置更多.我希望它能尽快完成,但是现在,正确设置会很痛苦. (4认同)
  • 也许是因为Arquillian很慢并需要一个正在运行的容器? (3认同)