Тестирование EJB с помощью JUnit - PullRequest
39 голосов
/ 24 июня 2011

Как мне протестировать EJB 3.1, который получает экземпляр EntityManager?

Возможный 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());
}

Я использую только GlassFish 3.1 и Eclipse Indigo для разработчиков 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.

Ответы [ 4 ]

79 голосов
/ 17 декабря 2013

Принятый ответ требует большого количества кода, включая постоянный уровень.Вместо этого используйте встроенный контейнер для проверки реальных бинов;в противном случае при имитации уровня персистентности получается код, который едва ли тестирует что-либо полезное.

Использование сессионного компонента с менеджером сущностей, который ссылается на единицу сохранения:

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

Компонент объекта:

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

Затем добавьте две зависимости (например, для 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>

Имеет зависимости , сеанс и сущность bean, сохраняемость файл, проверка файлов, реализованных в точности так, как показано, тогда тесты должныпроходить.(Примеры в Интернете крайне неадекватны.)

41 голосов
/ 27 июня 2011

Прежде всего, убедитесь, что вы различаете модульные тесты и интеграционные тесты .JUnit - это просто инфраструктура, которая помогает вам организовывать и запускать тесты, но вы должны определить объем ваших тестов.

Я предполагаю, что вы заинтересованы в определении unit test CommentService.findAll().Что это значит?Это означает, что я проверю, что вызов метода findAll() приводит к тому, что CommentService вызывает именованный запрос, названный строковой константой FIND_ALL.

Благодаря внедрению зависимостей и созданию заглушек вы можете легко добиться этого с помощью, например1014 * 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!
    }
}

С помощью вышеописанного модульного теста я протестировал поведение реализации findAll().Модульный тест подтвердил, что правильный именованный запрос получен и что результат, возвращенный именованным запросом, был возвращен вызываемому объекту.

Более того, приведенный выше модульный тест подтверждает, что реализация findAll() является правильной независимоосновного поставщика JPA и базовых данных.Я не хочу тестировать JPA и JPA-провайдера, если не подозреваю, что в стороннем коде есть ошибки, поэтому устранение этих зависимостей позволяет мне полностью сосредоточить тест на бизнес-логике сервиса Comment.

Может потребоваться некоторое время, чтобы приспособиться к образу поведения при тестировании с использованием заглушек, но это очень мощный метод для тестирования бизнес-логики ваших компонентов EJB 3.1, поскольку он позволяет изолировать и сузить область действия каждого теста для исключения внешних зависимостей..

9 голосов
/ 30 августа 2012

Почему бы не использовать Arquillian для написания даже модульных тестов и запуска их в реальном контейнере!?

Не надо больше издеваться. Нет больше жизненного цикла контейнера и проблем с развертыванием. Просто настоящие тесты!

Моты могут быть тактическими, но чаще всего они используются, чтобы заставить код работать вне реальной среды. Аркиллиан, давай ты угробишь насмешки и напишешь реальные тесты. Это потому, что Arquillian переносит ваш тест во время выполнения, предоставляя вам доступ к ресурсам контейнера, содержательную обратную связь и понимание того, как на самом деле работает код.

Подробнее о Особенности Arquillian.

1 голос
/ 27 июня 2011

Можно написать модульные тесты, которые запускаются для контейнера, но предостережение заключается в том, что контейнер / сервер приложений должен быть включен Поскольку это не очень практично, общий подход заключается в использовании «фиктивного» контейнера для запуска ваших юнит-тестов. Для этого проверьте JUnitEE или ejb3unit:

JUnitEE

ejb3unit

...