单元测试中UI.access()内视图变更未推送问题求助
Vaadin单元测试异步UI更新断言失败的问题处理
问题场景
你编写的NewsAdminView通过NewsService返回的Mono<List<News>>异步更新Grid,生产环境(带@Push注解)运行正常,但在继承VaadinUnitTest的单元测试中,虽然调试可见updateItems()已被调用、Grid数据和启用状态已设置,但断言Grid的启用状态和数据行数均失败。
视图代码
@Route(value = NewsAdminView.ROUTE) @RequiredArgsConstructor public class NewsAdminView extends VerticalLayout { public static final String ROUTE = ""; private final NewsService service; final Grid<News> grid = new Grid<>(News.class); @Override protected void onAttach(AttachEvent attachEvent) { addComponents(); service.getNews().subscribe(items -> updateItems(attachEvent.getUI(), items)); } private void updateItems(final UI ui, final List<News> news) { ui.access(() -> { grid.setItems(news); grid.setEnabled(true); }); } // 省略组件初始化、安全、国际化等细节 }
测试代码
class NewsAdminViewTest extends VaadinUnitTest { // VaadinUnitTest 继承 Vaadin 的 SpringUIUnitTest @MockBean private NewsService service; private News news; @BeforeEach void setUp() { news = News.builder().build(); when(service.getNews()).thenReturn(Mono.just(List.of(news))); } @Test void openWithNoParameters() { final NewsAdminView view = navigate(NewsAdminView.ROUTE, NewsAdminView.class); assertThat(view.grid.isVisible()).isTrue(); assertThat(view.grid.isEnabled()).isTrue(); assertThat(view.grid.getDataProvider().size(new Query<>())).isEqualTo(1); } }
问题原因
这不是单元测试不支持的场景,而是异步操作时机与测试上下文不匹配导致的:
Mono.subscribe()是异步执行的,测试方法的断言逻辑会在回调完成前执行,此时Grid的更新还未完成。- 单元测试环境中,
VaadinSession的UI对象上下文与生产环境不同:UI.access()虽然执行了更新代码,但测试框架没有触发UI变更的同步逻辑,且无真实客户端接收推送,导致变更无法被断言捕捉。
解决方案
方案1:同步等待异步操作完成
在测试中阻塞等待Mono执行完毕,确保回调完成后再执行断言:
@Test void openWithNoParameters() { final NewsAdminView view = navigate(NewsAdminView.ROUTE, NewsAdminView.class); // 阻塞等待异步流完成,确保updateItems回调执行完毕 service.getNews().block(); assertThat(view.grid.isVisible()).isTrue(); assertThat(view.grid.isEnabled()).isTrue(); assertThat(view.grid.getDataProvider().size(new Query<>())).isEqualTo(1); }
方案2:手动触发UI更新逻辑
跳过异步调用,直接调用updateItems方法测试UI更新逻辑(单元测试聚焦视图本身的逻辑,而非service的异步流程):
@Test void openWithNoParameters() { final NewsAdminView view = navigate(NewsAdminView.ROUTE, NewsAdminView.class); // 手动触发UI更新,避免异步时机问题 view.updateItems(UI.getCurrent(), List.of(news)); assertThat(view.grid.isVisible()).isTrue(); assertThat(view.grid.isEnabled()).isTrue(); assertThat(view.grid.getDataProvider().size(new Query<>())).isEqualTo(1); }
方案3:强制同步UI上下文
利用UI.accessSynchronously()确保UI变更被同步处理:
@Test void openWithNoParameters() { final NewsAdminView view = navigate(NewsAdminView.ROUTE, NewsAdminView.class); // 强制同步UI上下文,确保所有pending的UI更新被处理 UI.getCurrent().accessSynchronously(() -> {}); assertThat(view.grid.isVisible()).isTrue(); assertThat(view.grid.isEnabled()).isTrue(); assertThat(view.grid.getDataProvider().size(new Query<>())).isEqualTo(1); }
内容的提问来源于stack exchange,提问作者Sebastian Lütge
相关产品推荐
相关产品推荐

