Flutter用Mockito模拟Riverpod Provider报错,求无改真实类的测试方案
Flutter中Mockito模拟Riverpod Provider的测试问题解决
问题场景
在测试Riverpod的TodoList Notifier时,尝试用Mockito模拟依赖的userItemProvider,先后遇到两个报错:
NoSuchMethodError: Class 'MockUserItem' has no instance method '_setElement'type 'Null' is not a subtype of type 'User'
希望找到无需修改业务代码的测试方案。
依赖版本
# pubspec.yaml environment: sdk: ^3.5.2 dependencies: flutter: sdk: flutter flutter_riverpod: ^2.5.1 riverpod_annotation: ^2.3.5 freezed_annotation: ^2.4.4 dev_dependencies: flutter_test: sdk: flutter riverpod_generator: ^2.4.3 riverpod_lint: ^2.3.13 freezed: ^2.5.7 mockito: ^5.4.4
业务代码
TodoList Notifier(待测试)
// notifier_provider_page.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_flutter/user_item_provider.dart'; import 'todo.dart'; part 'notifier_provider_page.g.dart'; @riverpod class TodoList extends _$TodoList { @override List<Todo> build() { return []; } void add(Todo todo) { final user = ref.read(userItemProvider); if (user?.email == 'a') { state = [...state, todo]; } } }
UserItem Notifier(依赖)
// user_item_provider.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'user.dart'; part 'user_item_provider.g.dart'; @riverpod class UserItem extends _$UserItem { @override User build() { return const User(name: "a", email: "b", password: "c"); } void add(User user) { state = user; } }
初始测试代码及报错
测试代码
// notifier_provider_page_test.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:riverpod_flutter/notifier_provider_page.dart'; import 'package:riverpod_flutter/todo.dart'; import 'package:riverpod_flutter/user_item_provider.dart'; import 'notifier_provider_page_test.mocks.dart'; @GenerateNiceMocks([MockSpec<UserItem>()]) void main() { late ProviderContainer container; final MockUserItem mockUserItem = MockUserItem(); setUp(() { container = ProviderContainer(overrides: [ userItemProvider.overrideWith(() { return mockUserItem; }) ]); }); tearDown(() { container.dispose(); }); test('test', () { final todo = container.read(todoListProvider.notifier); todo.add(const Todo( userId: 1, id: 1, title: 'title', )); expect(container.read(todoListProvider), [const Todo(userId: 1, id: 1, title: 'title')]); }); }
首次报错
NoSuchMethodError: Class 'MockUserItem' has no instance method '_setElement'. Receiver: Instance of 'MockUserItem' Tried calling: _setElement(Instance of 'AutoDisposeNotifierProviderElement<UserItem, User>')
修改尝试后的新报错
在业务文件中添加自定义Mock类后,出现:
type 'Null' is not a subtype of type 'User'
无需修改业务代码的解决方案
方案一:直接Override Provider返回测试状态
因为TodoList的add方法仅依赖userItemProvider的状态值,无需Mock整个Notifier,直接通过overrideWith返回测试用的User状态即可:
// notifier_provider_page_test.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod_flutter/notifier_provider_page.dart'; import 'package:riverpod_flutter/todo.dart'; import 'package:riverpod_flutter/user_item_provider.dart'; void main() { late ProviderContainer container; setUp(() { container = ProviderContainer(overrides: [ userItemProvider.overrideWith((ref) { // 返回符合测试条件的User对象 return UserItem()..state = const User(name: "a", email: "a", password: "c"); }) ]); }); tearDown(() { container.dispose(); }); test('当用户邮箱为"a"时,成功添加Todo', () { final todoNotifier = container.read(todoListProvider.notifier); todoNotifier.add(const Todo(userId: 1, id: 1, title: 'title')); expect(container.read(todoListProvider), [const Todo(userId: 1, id: 1, title: 'title')]); }); test('当用户邮箱不为"a"时,不添加Todo', () { final testContainer = ProviderContainer(overrides: [ userItemProvider.overrideWith((ref) { return UserItem()..state = const User(name: "a", email: "b", password: "c"); }) ]); final todoNotifier = testContainer.read(todoListProvider.notifier); todoNotifier.add(const Todo(userId: 1, id: 1, title: 'title')); expect(testContainer.read(todoListProvider), []); testContainer.dispose(); }); }
方案二:测试文件内自定义Mock类(不修改业务代码)
若需用Mockito验证方法调用,可在测试文件内创建Mock类,继承自动生成的_$UserItem并实现build方法:
// notifier_provider_page_test.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:riverpod_flutter/notifier_provider_page.dart'; import 'package:riverpod_flutter/todo.dart'; import 'package:riverpod_flutter/user_item_provider.dart'; // Mock类仅存在于测试文件,不污染业务代码 class MockUserItem extends _$UserItem with Mock { @override User build() { // 返回非Null的测试用User,避免类型转换错误 return const User(name: "a", email: "a", password: "c"); } } void main() { late ProviderContainer container; late MockUserItem mockUserItem; setUp(() { mockUserItem = MockUserItem(); // 可按需Mock add方法的行为 when(mockUserItem.add(any)).thenAnswer((_) => null); container = ProviderContainer(overrides: [ userItemProvider.overrideWith(() => mockUserItem), ]); }); tearDown(() { container.dispose(); }); test('测试添加Todo逻辑', () { final todoNotifier = container.read(todoListProvider.notifier); todoNotifier.add(const Todo(userId: 1, id: 1, title: 'title')); expect(container.read(todoListProvider), [const Todo(userId: 1, id: 1, title: 'title')]); // 验证Mock方法调用(可选) // verify(mockUserItem.add(any)).neverCalled(); }); }
错误原因解析
_setElement方法缺失:Riverpod的Notifier内部包含_setElement等私有初始化方法,Mockito自动生成的Mock类未实现这些方法,导致Provider初始化失败。- Null类型转换错误:自定义Mock类继承
AutoDisposeNotifier但未实现build方法,默认返回Null,不符合UserItem要求的非Null返回值。
内容的提问来源于stack exchange,提问作者akkie
相关产品推荐
相关产品推荐

