You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试中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);
    }
}

问题原因

这不是单元测试不支持的场景,而是异步操作时机与测试上下文不匹配导致的:

  1. Mono.subscribe()是异步执行的,测试方法的断言逻辑会在回调完成前执行,此时Grid的更新还未完成。
  2. 单元测试环境中,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 05:37:41