4.9.2. Интеграционные тесты веб-уровня
Интеграционные тесты веб-уровня выполняются в контейнере Spring блока Web Client. Тестовый контейнер работает независимо от среднего слоя, так как фреймворк автоматически создает заглушки (stubs) для всех сервисов. Тестовая инфраструктура состоит из следующих классов, расположенных в пакете com.haulmont.cuba.web.testsupport
и его вложенных пакетах:
-
TestContainer
- обертка вокруг контейнера Spring для использования в качестве базового класса в проектах. -
TestServiceProxy
- предоставляет заглушки по умолчанию для сервисов среднего слоя. Данный класс можно также использовать для регистрации моков сервисов, специфичных для теста: см. статический методmock()
. -
DataServiceProxy
- заглушка по умолчанию дляDataManager
. Содержит реализацию методаcommit()
, которая имитирует поведение реального хранилища: делает новые экземпляры сущностей detached, инкрементирует версии, и т.д. Методы загрузки ничего не делают и возвращают null и пустые коллекции. -
TestUiEnvironment
- предоставляет набор методов для конфигурирования и полученияTestContainer
. Экземпляр данного класса должен быть использован в тестах в качестве JUnit 5 extension. -
TestEntityFactory
- фабрика для удобного создания тестовых экземпляров сущностей. Может быть получена изTestContainer
.
Несмотря на то, что фреймворк предоставляет дефолтные заглушки для сервисов среднего слоя, в тестах могут понадобится собственные моки сервисов. Для создания моков можно использовать любой мок-фреймворк, просто подключив его в зависимости проекта как описано в разделе выше. Моки сервисов регистрируются с помощью метода TestServiceProxy.mock()
.
- Пример контейнера для интеграционных тестов веб-уровня
-
Создайте каталог
test
в модулеweb
. Затем создайте в подходящем пакете данного каталога класс контейнера:package com.company.demo; import com.haulmont.cuba.web.testsupport.TestContainer; import java.util.Arrays; public class DemoWebTestContainer extends TestContainer { public DemoWebTestContainer() { appComponents = Arrays.asList( "com.haulmont.cuba" // add CUBA add-ons and custom app components here ); appPropertiesFiles = Arrays.asList( // List the files defined in your web.xml // in appPropertiesConfig context parameter of the web module "com/company/demo/web-app.properties", // Add this file which is located in CUBA and defines some properties // specifically for test environment. You can replace it with your own // or add another one in the end. "com/haulmont/cuba/web/testsupport/test-web-app.properties" ); } public static class Common extends DemoWebTestContainer { // A common singleton instance of the test container which is initialized once for all tests public static final DemoWebTestContainer.Common INSTANCE = new DemoWebTestContainer.Common(); private static volatile boolean initialized; private Common() { } @Override public void before() throws Throwable { if (!initialized) { super.before(); initialized = true; } setupContext(); } @Override public void after() { cleanupContext(); // never stops - do not call super } } }
- Пример теста экрана UI
-
Ниже приведен пример теста, который проверяет состояние редактируемой в экране сущности после некоторого действия пользователя.
package com.company.demo.customer; import com.company.demo.DemoWebTestContainer; import com.company.demo.entity.Customer; import com.company.demo.web.screens.customer.CustomerEdit; import com.haulmont.cuba.gui.Screens; import com.haulmont.cuba.gui.components.Button; import com.haulmont.cuba.gui.screen.OpenMode; import com.haulmont.cuba.web.app.main.MainScreen; import com.haulmont.cuba.web.testsupport.TestEntityFactory; import com.haulmont.cuba.web.testsupport.TestEntityState; import com.haulmont.cuba.web.testsupport.TestUiEnvironment; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; public class CustomerEditInteractionTest { @RegisterExtension TestUiEnvironment environment = new TestUiEnvironment(DemoWebTestContainer.Common.INSTANCE).withUserLogin("admin"); (1) private Customer customer; @BeforeEach public void setUp() throws Exception { TestEntityFactory<Customer> customersFactory = environment.getContainer().getEntityFactory(Customer.class, TestEntityState.NEW); customer = customersFactory.create(Collections.emptyMap()); (2) } @Test public void testGenerateName() { Screens screens = environment.getScreens(); (3) screens.create(MainScreen.class, OpenMode.ROOT).show(); (4) CustomerEdit customerEdit = screens.create(CustomerEdit.class); (5) customerEdit.setEntityToEdit(customer); customerEdit.show(); assertNull(customerEdit.getEditedEntity().getName()); Button generateBtn = (Button) customerEdit.getWindow().getComponent("generateBtn"); (6) customerEdit.onGenerateBtnClick(new Button.ClickEvent(generateBtn)); (7) assertEquals("Generated name", customerEdit.getEditedEntity().getName()); } }
1 - создание экземпляра TestUiEnvironment
с разделяемым контейнером и пользователемadmin
в заглушке пользовательской сессии.2 - создание экземпляра сущности в состоянии new
.3 - получение инфраструктурного объекта Screens
.4 - открытие главного экрана, который необходим для открытия остальных экранов приложения. 5 - создание, инициализация и открытие экрана редактирования сущности. 6 - получение компонента Button
.7 - создание объекта события нажатия кнопки и вызов метода контроллера, который реагирует на это событие.
- Пример теста загрузки данных в экран
-
Ниже приведен пример теста, который проверяет корректность загрузки данных в экране UI.
package com.company.demo.customer; import com.company.demo.DemoWebTestContainer; import com.company.demo.entity.Customer; import com.company.demo.web.screens.customer.CustomerEdit; import com.haulmont.cuba.core.app.DataService; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.LoadContext; import com.haulmont.cuba.gui.Screens; import com.haulmont.cuba.gui.model.InstanceContainer; import com.haulmont.cuba.gui.screen.OpenMode; import com.haulmont.cuba.gui.screen.UiControllerUtils; import com.haulmont.cuba.web.app.main.MainScreen; import com.haulmont.cuba.web.testsupport.TestEntityFactory; import com.haulmont.cuba.web.testsupport.TestEntityState; import com.haulmont.cuba.web.testsupport.TestUiEnvironment; import com.haulmont.cuba.web.testsupport.proxy.TestServiceProxy; import mockit.Delegate; import mockit.Expectations; import mockit.Mocked; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.jupiter.api.Assertions.assertEquals; public class CustomerEditLoadDataTest { @RegisterExtension TestUiEnvironment environment = new TestUiEnvironment(DemoWebTestContainer.Common.INSTANCE).withUserLogin("admin"); (1) @Mocked private DataService dataService; (1) private Customer customer; @BeforeEach public void setUp() throws Exception { new Expectations() {{ (2) dataService.load((LoadContext<? extends Entity>) any); result = new Delegate() { Entity load(LoadContext lc) { if ("demo_Customer".equals(lc.getEntityMetaClass())) { return customer; } else return null; } }; }}; TestServiceProxy.mock(DataService.class, dataService); (3) TestEntityFactory<Customer> customersFactory = environment.getContainer().getEntityFactory(Customer.class, TestEntityState.DETACHED); customer = customersFactory.create( "name", "Homer", "email", "homer@simpson.com"); (4) } @AfterEach public void tearDown() throws Exception { TestServiceProxy.clear(); (5) } @Test public void testLoadData() { Screens screens = environment.getScreens(); screens.create(MainScreen.class, OpenMode.ROOT).show(); CustomerEdit customerEdit = screens.create(CustomerEdit.class); customerEdit.setEntityToEdit(customer); customerEdit.show(); InstanceContainer customerDc = UiControllerUtils.getScreenData(customerEdit).getContainer("customerDc"); (6) assertEquals(customer, customerDc.getItem()); } }
1 - определение мока с помощью фреймворка JMockit. 2 - задание поведения мока. 3 - регистрация мока. 4 - создание экземпляра сущности в состоянии detached
.5 - удаление мока после завершения теста. 6 - получение контейнера данных.