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

Flutter Widget测试:ViewModel回调Stub失效,如何触发变量切换?

解决Flutter Widget测试中Mockito Stub回调未执行的问题

问题描述

你需要为以下InkWell组件编写Widget测试:

InkWell(
      key: const Key(kDraftTileKey),
      onTap: () {
        context.read<CreatorHubTapViewModel>().startPublishingFlowAgain(startPublishingFlowAgainPressed: startPublishingFlowAgainPressed);
      },
      child:...
    );

点击该组件时会调用如下函数:

void startPublishingFlowAgainPressed() {
    widget.viewModel.saveNFT(nft: widget.nft);
    Navigator.of(context).pushNamed(RouteUtil.kRouteHome);
}

你已经用Mockito创建了CreatorHubTapViewModel的Mock类:

class CreatorHubTapViewModel extends ChangeNotifier {
  void onViewOnPylons({required VoidCallback onViewOnPylonsPressed}) {
    onViewOnPylonsPressed.call();
  }

  void startPublishingFlowAgain({required VoidCallback startPublishingFlowAgainPressed}) {
    startPublishingFlowAgainPressed.call();
  }
}

当前测试中,你尝试StubstartPublishingFlowAgain方法,希望触发回调将clicked变量设为true,但Stub的thenAnswer和回调本身都未执行:

when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: () {
    clicked = true;
})).thenAnswer((realInvocation) {});

请问如何实现目标,即在该函数调用时切换测试变量?


问题原因

你当前的Stub写法用了具体的回调实例作为参数匹配条件,但Widget实际传入的startPublishingFlowAgainPressed是另一个完全不同的回调实例,Mockito无法匹配到对应的调用,所以Stub逻辑不会执行。

解决方案

使用Mockito的any匹配器来匹配回调参数,然后在thenAnswer中处理变量或执行真实回调:

方法1:执行真实回调并设置测试变量

如果需要保留原回调的逻辑(比如saveNFT调用和路由跳转),可以在Stub中获取真实传入的回调并执行,同时修改测试变量:

bool clicked = false;

// 使用anyNamed匹配命名参数
when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed')))
   .thenAnswer((invocation) {
      // 获取真实调用传入的回调并执行
      final callback = invocation.namedArguments[Symbol('startPublishingFlowAgainPressed')] as VoidCallback;
      callback();
      // 设置测试变量
      clicked = true;
    });

// 触发InkWell点击
await tester.tap(find.byKey(const Key(kDraftTileKey)));
await tester.pumpAndSettle();

// 验证变量状态
expect(clicked, isTrue);
// 验证ViewModel方法是否被调用
verify(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))).called(1);

方法2:直接在Stub中修改变量(无需执行原回调)

如果只需要验证方法被调用,不需要执行原回调的业务逻辑,可以直接在Stub里修改测试变量:

bool clicked = false;

when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed')))
   .thenAnswer((_) {
      clicked = true;
    });

// 触发点击并验证
await tester.tap(find.byKey(const Key(kDraftTileKey)));
await tester.pumpAndSettle();

expect(clicked, isTrue);
verify(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))).called(1);

额外验证逻辑(可选)

如果需要验证回调内的saveNFT调用和路由跳转,可以单独对这些逻辑做测试:

// 对saveNFT方法做Stub
when(viewModel.saveNFT(nft: anyNamed('nft'))).thenAnswer((_) {});

// 触发点击
await tester.tap(find.byKey(const Key(kDraftTileKey)));
await tester.pumpAndSettle();

// 验证saveNFT被调用
verify(viewModel.saveNFT(nft: anyNamed('nft'))).called(1);
// 验证路由跳转(示例:检查是否跳转到Home页面)
expect(find.byType(HomeScreen), findsOneWidget);

内容的提问来源于stack exchange,提问作者Ahmad hassan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:05:28