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

blocTest中when().thenAnswer()未触发,单元测试异常求助

Mockito Mock对象未生效导致Cubit单元测试失败

问题现象

使用Mockito框架及@GenerateNiceMocks注解编写Cubit函数单元测试时,所有when().thenAnswer()语句均未生效。调试发现待测试函数内的本地变量全为null,而非预设值,测试最终报错。

待测试函数

Future<void> loadStoredCredentials() async {
  String? email = await storage.read(key: AuthenticationKeys.rememberEmail);
  String? str = await storage.read(key: AuthenticationKeys.rememberMeKey);
  String? lastViewedMessages = await storage.read(
    key: 'lastViewedMessages',
  );

  bool rememberMe = false;
  if (str != null) {
    rememberMe = true;
  }

  if (email != null) {
    emit(state.copyWith(
      username: email,
      usernameValid: FieldValidity.valid,
      rememberMe: rememberMe,
      lastViewedMessages: lastViewedMessages,
    ));
  }
}

原测试代码

@GenerateNiceMocks([MockSpec<FlutterSecureStorage>()])

void main() async {
  TestWidgetsFlutterBinding.ensureInitialized();

  SignInCubit? signInCubit;

  setUp(() async {
    signInCubit = SignInCubit();
  });

  group('MockFlutterSecureStorage tests -', () {
    setUpAll(() => FlutterSecureStorage.setMockInitialValues({}));

    final email = Future.value('test@test.test');
    final flag = Future.value('true');
    final nullFuture = Future.value('');

    blocTest<SignInCubit, SignInCubitState>(
      'loadStoredCredentials - success',
      build: () => SignInCubit(),
      act: (cubit) async {
        final storage = MockFlutterSecureStorage();

        when(storage.read(key: AuthenticationKeys.rememberEmail))
            .thenAnswer((_) => email);
        when(storage.read(key: AuthenticationKeys.rememberMeKey))
            .thenAnswer((_) => flag);
        when(storage.read(key: 'lastViewedMessages'))
            .thenAnswer((_) => nullFuture);

        await cubit.loadStoredCredentials();
      },
      expect: () => (isA<SignInCubitState>),
    );
  });
}

测试报错信息

Expected: <Closure: () => TypeMatcher<SignInCubitState> from Function 'isA': static.>
  Actual: []

package:test_api                             expect
package:bloc_test/src/bloc_test.dart 220:16  testBloc.<fn>
===== asynchronous gap ===========================
dart:async                                   _CustomZone.registerUnaryCallback
package:bloc_test/src/bloc_test.dart 201:7   testBloc.<fn>
dart:async                                   runZonedGuarded
package:bloc_test/src/bloc_test.dart 199:9   testBloc
package:bloc_test/src/bloc_test.dart 156:13  blocTest.<fn>

解决方案

1. 修改SignInCubit构造函数,支持依赖注入

让Cubit的构造函数接受一个可选的FlutterSecureStorage参数,默认使用真实实例,测试时传入Mock实例:

class SignInCubit extends Cubit<SignInCubitState> {
  final FlutterSecureStorage storage;

  SignInCubit({FlutterSecureStorage? storage})
      : storage = storage ?? FlutterSecureStorage(),
        super(SignInCubitState.initial());

  // 保留原有的loadStoredCredentials方法
}

2. 调整测试代码,注入Mock实例并正确配置行为

在build函数中创建Mock实例,配置其行为后注入到Cubit中;同时修正expect部分的Matcher写法:

@GenerateNiceMocks([MockSpec<FlutterSecureStorage>()])

void main() async {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('MockFlutterSecureStorage tests -', () {
    setUpAll(() => FlutterSecureStorage.setMockInitialValues({}));

    blocTest<SignInCubit, SignInCubitState>(
      'loadStoredCredentials - success',
      build: () {
        final mockStorage = MockFlutterSecureStorage();
        
        // 配置Mock的read方法返回预设值
        when(mockStorage.read(key: AuthenticationKeys.rememberEmail))
            .thenAnswer((_) => Future.value('test@test.test'));
        when(mockStorage.read(key: AuthenticationKeys.rememberMeKey))
            .thenAnswer((_) => Future.value('true'));
        when(mockStorage.read(key: 'lastViewedMessages'))
            .thenAnswer((_) => Future.value(''));
        
        // 将Mock实例注入Cubit
        return SignInCubit(storage: mockStorage);
      },
      act: (cubit) async => await cubit.loadStoredCredentials(),
      expect: () => [
        // 精确验证状态属性
        isA<SignInCubitState>()
            .having((s) => s.username, 'username', 'test@test.test')
            .having((s) => s.rememberMe, 'rememberMe', true)
            .having((s) => s.lastViewedMessages, 'lastViewedMessages', ''),
      ],
    );
  });
}

问题根源说明

  • 原测试中在act里创建的Mock实例是局部变量,和Cubit内部使用的storage不是同一个对象,导致配置的Mock行为完全没被用到,Cubit仍然调用真实的FlutterSecureStorage方法,返回null。
  • 原expect部分写法错误,isA<SignInCubitState>是一个函数,需要调用它生成Matcher,并且要放在列表中返回,否则会把闭包当作预期值,导致匹配失败。

内容的提问来源于stack exchange,提问作者Max Walters

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 14:02:53