4.8.2. Web 集成测试

Web 集成测试运行在 Web 客户端 block 的 Spring 容器中。测试容器独立于中间件工作,因为框架会自动为所有中间件服务创建桩代码。测试基础设施由 com.haulmont.cuba.web.testsupport 及其内部包的下列类组成:

  • TestContainer - Spring 容器的包装器,用来作为项目特定容器的基类。

  • TestServiceProxy - 为中间件服务提供默认的桩代码。该类可以用来注册为特定用例 mock 的服务,参考其 mock() 静态方法。

  • DataServiceProxy - DataManager 的默认桩代码。其包含一个 commit() 方法的实现,能模拟真正的数据存储的行为:能让新实体 detach,增加实体版本,等等。加载方法返回 null 和空集合。

  • TestUiEnvironment - 提供一组方法用来配置和获取 `TestContainer。该类的实例在测试中需要作为 JUnit Rule 来使用。

  • TestEntityFactory - 测试中为方便创建实体实例的工厂。可以通过 TestContainer 获取工厂。

尽管框架为服务提供了默认桩代码,但是在测试中也许需要自己创建服务的 mock。要创建 mock,可以使用任何 mocking 框架,通过添加其为依赖即可,如上节所说。服务的 mock 均使用 TestServiceProxy.mock() 方法注册。

Web 集成测试容器示例

web 模块创建 test 目录。然后在 test 目录合适的包内创建项目的测试容器类:

  1. package com.company.sales.web;
  2. import com.haulmont.cuba.web.testsupport.TestContainer;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. public class SalesWebTestContainer extends TestContainer {
  6. public SalesWebTestContainer() {
  7. appComponents = new ArrayList<>(Arrays.asList(
  8. "com.haulmont.cuba"
  9. // add CUBA add-ons and custom app components here
  10. ));
  11. appPropertiesFiles = Arrays.asList(
  12. // List the files defined in your web.xml
  13. // in appPropertiesConfig context parameter of the web module
  14. "com/company/sales/web-app.properties",
  15. // Add this file which is located in CUBA and defines some properties
  16. // specifically for test environment. You can replace it with your own
  17. // or add another one in the end.
  18. "com/haulmont/cuba/web/testsupport/test-web-app.properties"
  19. );
  20. }
  21. public static class Common extends SalesWebTestContainer {
  22. // A common singleton instance of the test container which is initialized once for all tests
  23. public static final SalesWebTestContainer.Common INSTANCE = new SalesWebTestContainer.Common();
  24. private static volatile boolean initialized;
  25. private Common() {
  26. }
  27. @Override
  28. public void before() throws Throwable {
  29. if (!initialized) {
  30. super.before();
  31. initialized = true;
  32. }
  33. setupContext();
  34. }
  35. @Override
  36. public void after() {
  37. cleanupContext();
  38. // never stops - do not call super
  39. }
  40. }
  41. }

UI 界面测试示例

下面是 Web 集成测试的示例,在一些用户操作之后检查了编辑实体的状态。

  1. package com.company.sales.web.customer;
  2. import com.company.sales.entity.Customer;
  3. import com.company.sales.web.SalesWebTestContainer;
  4. import com.haulmont.cuba.gui.Screens;
  5. import com.haulmont.cuba.gui.components.Button;
  6. import com.haulmont.cuba.gui.screen.OpenMode;
  7. import com.haulmont.cuba.web.app.main.MainScreen;
  8. import com.haulmont.cuba.web.testsupport.TestEntityFactory;
  9. import com.haulmont.cuba.web.testsupport.TestEntityState;
  10. import com.haulmont.cuba.web.testsupport.TestUiEnvironment;
  11. import org.junit.Before;
  12. import org.junit.Rule;
  13. import org.junit.Test;
  14. import java.util.Collections;
  15. import static org.junit.Assert.*;
  16. public class CustomerEditInteractionTest {
  17. @Rule
  18. public TestUiEnvironment environment =
  19. new TestUiEnvironment(SalesWebTestContainer.Common.INSTANCE).withUserLogin("admin"); (1)
  20. private Customer customer;
  21. @Before
  22. public void setUp() throws Exception {
  23. TestEntityFactory<Customer> customersFactory =
  24. environment.getContainer().getEntityFactory(Customer.class, TestEntityState.NEW);
  25. customer = customersFactory.create(Collections.emptyMap()); (2)
  26. }
  27. @Test
  28. public void testGenerateName() {
  29. Screens screens = environment.getScreens(); (3)
  30. screens.create(MainScreen.class, OpenMode.ROOT).show(); (4)
  31. CustomerEdit customerEdit = screens.create(CustomerEdit.class); (5)
  32. customerEdit.setEntityToEdit(customer);
  33. customerEdit.show();
  34. assertNull(customerEdit.getEditedEntity().getName());
  35. Button generateBtn = (Button) customerEdit.getWindow().getComponent("generateBtn"); (6)
  36. customerEdit.onGenerateBtnClick(new Button.ClickEvent(generateBtn)); (7)
  37. assertEquals("Generated name", customerEdit.getEditedEntity().getName());
  38. }
  39. }
1- 定义带共享容器和带有 admin 的用户会话存根的测试环境。
2- 创建 new 状态的实体实例。
3- 从环境获取 Screens 基础设施对象。
4- 打开主界面,打开应用程序界面必须的步骤。
5- 创建、初始化并打开实体编辑界面。
6- 获取 Button 组件。
7- 创建一个点击事件,并以调用控制器方法的方式响应点击操作。

测试在界面加载数据的示例

下面是一个 web 集成测试的示例,检查加载数据的正确性。

  1. package com.company.sales.web.customer;
  2. import com.company.sales.entity.Customer;
  3. import com.company.sales.web.SalesWebTestContainer;
  4. import com.haulmont.cuba.core.app.DataService;
  5. import com.haulmont.cuba.core.entity.Entity;
  6. import com.haulmont.cuba.core.global.LoadContext;
  7. import com.haulmont.cuba.gui.Screens;
  8. import com.haulmont.cuba.gui.model.InstanceContainer;
  9. import com.haulmont.cuba.gui.screen.OpenMode;
  10. import com.haulmont.cuba.gui.screen.UiControllerUtils;
  11. import com.haulmont.cuba.web.app.main.MainScreen;
  12. import com.haulmont.cuba.web.testsupport.TestEntityFactory;
  13. import com.haulmont.cuba.web.testsupport.TestEntityState;
  14. import com.haulmont.cuba.web.testsupport.TestUiEnvironment;
  15. import com.haulmont.cuba.web.testsupport.proxy.TestServiceProxy;
  16. import mockit.Delegate;
  17. import mockit.Expectations;
  18. import mockit.Mocked;
  19. import org.junit.After;
  20. import org.junit.Before;
  21. import org.junit.Rule;
  22. import org.junit.Test;
  23. import static org.junit.Assert.assertEquals;
  24. public class CustomerEditLoadDataTest {
  25. @Rule
  26. public TestUiEnvironment environment =
  27. new TestUiEnvironment(SalesWebTestContainer.Common.INSTANCE).withUserLogin("admin");
  28. @Mocked
  29. private DataService dataService; (1)
  30. private Customer customer;
  31. @Before
  32. public void setUp() throws Exception {
  33. new Expectations() {{ (2)
  34. dataService.load((LoadContext<? extends Entity>) any);
  35. result = new Delegate() {
  36. Entity load(LoadContext lc) {
  37. if ("sales_Customer".equals(lc.getEntityMetaClass())) {
  38. return customer;
  39. } else
  40. return null;
  41. }
  42. };
  43. }};
  44. TestServiceProxy.mock(DataService.class, dataService); (3)
  45. TestEntityFactory<Customer> customersFactory =
  46. environment.getContainer().getEntityFactory(Customer.class, TestEntityState.DETACHED);
  47. customer = customersFactory.create(
  48. "name", "Homer", "email", "homer@simpson.com"); (4)
  49. }
  50. @After
  51. public void tearDown() throws Exception {
  52. TestServiceProxy.clear(); (5)
  53. }
  54. @Test
  55. public void testLoadData() {
  56. Screens screens = environment.getScreens();
  57. screens.create(MainScreen.class, OpenMode.ROOT).show();
  58. CustomerEdit customerEdit = screens.create(CustomerEdit.class);
  59. customerEdit.setEntityToEdit(customer);
  60. customerEdit.show();
  61. InstanceContainer customerDc = UiControllerUtils.getScreenData(customerEdit).getContainer("customerDc"); (6)
  62. assertEquals(customer, customerDc.getItem());
  63. }
  64. }
1- 使用 JMockit framework 定义数据服务 mock。
2- 定义 mock 行为。
3- 注册 mock。
4- 创建 detached 状态的实体实例。
5- 测试完成后移除 mock。
6- 获取数据容器。