开发者

Testing an EJB with JUnit

How should I test an EJB 3.1 which gets an instance of EntityManager injected?

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

}

A possible test:

@T开发者_StackOverflow中文版est
public void testFindAll() {
    List<Comment> all = service.findAll();
    Assert.assertEquals(8, all.size());
}

I am only using GlassFish 3.1 and Eclipse Indigo for Java EE Developers. I already tried things like that:

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

But all I got was:

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


The accepted answer requires mocking a lot of code, including the persistence layer. Use an embedded container to test the actual beans, instead; otherwise, mocking the persistence layer results in code that barely tests anything useful.

Use a session bean with an entity manager that references a persistence unit:

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

The entity 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;
    }
}

This persistence unit is defined in the persistence.xml file as follows:

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

The transaction type must be JTA.

Then write a test that creates and destroys the EJB container (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());
    }
}

Next, add two dependencies (such as to a 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>

Having the dependencies, session and entity bean, persistence file, test files implemented exactly as shown, then the test(s) should pass. (The examples on the Internet are woefully inadequate.)


First of all, make sure you distinguish between unit tests and integration tests. JUnit is just a framework that helps you organize and run the tests, but you have to determine the scope of your tests.

I assume you're interested in defining a unit test of CommentService.findAll(). What does that mean? That means I'll verify that calling the findAll() method results in CommentService invoking the named query named by the FIND_ALL string constant.

Thanks to dependency injection and stubbing, you can easily achieve that using e.g. Mockito to stub out the EntityManager. For the unit test, we're only focusing on the business logic in findAll(), so I won't bother testing lookup of the Comment service either--testing that the Comment service can be looked up and is wired to a proper entity manager instance is in the scope of an integration test, not a unit test.

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

With the unit test above, I tested the behavior of the findAll() implementation. The unit test verified that the correct named query is obtained and that the result returned by the named query was returned to the callee.

What's more, the unit test above verifies that the implementation of findAll() is correct independently of the underlying JPA provider and the underlying data. I don't want to test JPA and the JPA provider unless I suspect there are bugs in the 3rd party code, so stubbing out these dependencies lets me focus the test entirely on the business logic of the Comment service.

It can take a little while to adjust to the mindset of testing behavior using stubs, but it is a very powerful technique for testing the business logic of your EJB 3.1 beans because it lets you isolate and narrow the scope of each test to exclude external dependencies.


Why not using Arquillian to write even unit tests and run them in a real container!?

No more mocks. No more container lifecycle and deployment hassles. Just real tests!

Mocks can be tactical, but more often than not, they are used to make code work outside of a real environment. Arquillian let's you ditch the mocks and write real tests. That's because Arquillian brings your test to the runtime, giving you access to container resources, meaningful feedback and insight about how the code really works.

More about Arquillian features.


It's possible to write unit tests that run against a container, but the caveat is that the container/appserver has to be up. Since that's not really practical, the general approach is to use a "mock" container to run your unit tests against. For that, check out JUnitEE or ejb3unit:

junitee

ejb3unit

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜