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

如何在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类,支持设置方法的返回值、验证调用次数等:

  1. 添加依赖:在pubspec.yaml的dev_dependencies中添加:
dev_dependencies:
  mockito: ^5.4.0
  build_runner: ^2.4.4
  1. 生成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
  1. 测试中使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 20:27:30