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
相关产品推荐
相关产品推荐

