如何在Dart中Mock替换服务?测试时替换UserSettings构造方式
如何在测试阶段替换依赖注入中的UserSettings实现并提供Mock服务
问题背景
我基于GetIt实现了依赖注入容器,相关代码如下:
依赖注入容器代码
import 'package:get_it/get_it.dart'; import 'package:myapp/functional/api.dart'; import 'package:myapp/functional/userSettings.dart'; // 依赖注入容器 final sl = GetIt.instance; void initDepedencyInjection() { sl.registerSingleton<Api>(Api()); sl.registerSingleton<UserSettings>(UserSettings()); }
Api类代码(functional/api.dart)
import 'package:http/http.dart' as http; class Api { final String __base_url="https://example.com"; final UserSettings settings = sl<UserSettings>(); Future<bool> login(String username, String password) async { final Uri url = Uri.parse(__base_url + "/token"); var response = await http.post(url, body: {'username':username,'password':password}); var responseJson = json.decode(response.body) as Map; await settings.saveSetting('token',responseJson['token']); return response.statusCode == 201; } }
UserSettings类代码(functional/UserSettings.dart)
import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:file/memory.dart'; class UserSettings { late final FileSystem _fs; UserSettings(): _fs = LocalFileSystem(); UserSettings.usingMemory(){ _fs = MemoryFileSystem(); } Future<void> saveSetting(String key, String value) async { // 具体实现 } }
我希望在测试阶段将UserSettings的构造方式替换为usingMemory(),同时想了解如何向依赖注入容器提供Mock服务。
解决方案
一、测试阶段替换为内存版UserSettings
GetIt支持在测试环境中重新注册依赖以覆盖原有实现,只需在测试初始化时重置容器并注册内存版的UserSettings:
方式1:测试文件内直接初始化
import 'package:test/test.dart'; import 'package:get_it/get_it.dart'; import 'package:myapp/functional/api.dart'; import 'package:myapp/functional/userSettings.dart'; void main() { setUp(() { // 重置容器,清除生产环境的注册,保证测试隔离 sl.reset(); // 先注册内存版UserSettings sl.registerSingleton<UserSettings>(UserSettings.usingMemory()); // 再注册Api(Api依赖UserSettings,需保证依赖先注册) sl.registerSingleton<Api>(Api()); }); // 测试用例... }
方式2:拆分生产/测试初始化逻辑
如果DI初始化逻辑复杂,可以拆分出单独的初始化函数,便于复用:
// 生产环境初始化 void initProductionDI() { sl.registerSingleton<UserSettings>(UserSettings()); sl.registerSingleton<Api>(Api()); } // 测试环境初始化 void initTestDI() { sl.reset(); sl.registerSingleton<UserSettings>(UserSettings.usingMemory()); sl.registerSingleton<Api>(Api()); }
测试时直接调用initTestDI()即可。
二、提供Mock服务的两种方法
如果需要替换为完全Mock的服务(比如避免真实网络请求、模拟特定返回值),可以通过以下两种方式实现:
方法1:自定义Mock类
直接编写继承自原类的Mock类,重写需要模拟的方法:
class MockApi extends Api { @override Future<bool> login(String username, String password) async { // 模拟登录成功,不发起真实网络请求 await sl<UserSettings>().saveSetting('token', 'mock_test_token'); return true; } }
然后在测试初始化时注册Mock类:
void initTestDI() { sl.reset(); sl.registerSingleton<UserSettings>(UserSettings.usingMemory()); sl.registerSingleton<Api>(MockApi()); }
方法2:使用Mockito生成Mock类
借助Mockito库可以更灵活地生成Mock类,支持设置方法的返回值、验证调用次数等:
- 添加依赖:在
pubspec.yaml的dev_dependencies中添加:
dev_dependencies: mockito: ^5.4.0 build_runner: ^2.4.4
- 生成Mock类:创建
test/mocks.dart文件:
import 'package:mockito/mockito.dart'; import 'package:myapp/functional/api.dart'; class MockApi extends Mock implements Api {}
运行命令生成Mock代码:
dart run build_runner build
- 测试中使用Mock:在测试初始化时注册Mock实例,并设置模拟逻辑:
void setUp() { sl.reset(); sl.registerSingleton<UserSettings>(UserSettings.usingMemory()); // 创建MockApi实例并注册 final mockApi = MockApi(); sl.registerSingleton<Api>(mockApi); // 设置login方法的模拟返回:任意用户名密码都返回登录成功 when(mockApi.login(any, any)).thenAnswer((_) async { await sl<UserSettings>().saveSetting('token', 'mock_token'); return true; }); }
这样测试中调用sl<Api>().login()时,会使用Mock的实现,完全隔离真实服务。
内容的提问来源于stack exchange,提问作者Dimitrios Desyllas
相关产品推荐
相关产品推荐

