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

Flutter/Dart无Mock的Repository单元测试方法及Mockito作用解析

Flutter/Dart中Repository的无Mock单元测试及Mockito的作用

一、不使用Mocking的单元测试(验证真实接口数据)

要直接调用真实接口测试Repository,核心是让测试代码依赖真实网络组件,但需先优化代码结构,再覆盖不同场景:

1. 先优化原Repository代码(修复类型问题+注入Dio)

原代码存在返回类型不一致的问题,同时将Dio改为构造注入,方便测试时统一配置:

class Repository {
  final Dio dio;
  final NetworkManager networkManager;
  
  Repository({required this.dio, required this.networkManager});

  Future<List<TodoModel>> getTodo() async {
    if (networkManager.isNetworkAvailable()) {
      final response = await dio.get('https://dummyjson.com/todos');
      return response.data['todos'].map<TodoModel>((item) {
        return TodoModel.fromJson(item);
      }).toList();
    } else {
      return const [TodoModel(id: 999, todo: "FailedTODO", completed: false, userId: 10101)];
    }
  }
}

class NetworkManager {
  bool isNetworkAvailable() {
    return true;
  }
}

// 假设的TodoModel定义
class TodoModel {
  final int id;
  final String todo;
  final bool completed;
  final int userId;

  const TodoModel({
    required this.id,
    required this.todo,
    required this.completed,
    required this.userId,
  });

  factory TodoModel.fromJson(Map<String, dynamic> json) {
    return TodoModel(
      id: json['id'],
      todo: json['todo'],
      completed: json['completed'],
      userId: json['userId'],
    );
  }
}

2. 编写无Mock的单元测试代码

使用test包编写测试,直接调用真实接口,同时覆盖网络可用/不可用两种场景:

import 'package:dio/dio.dart';
import 'package:test/test.dart';

void main() {
  late Repository repository;
  late Dio dio;

  setUp(() {
    dio = Dio();
    // 初始化网络可用场景的Repository
    repository = Repository(
      dio: dio,
      networkManager: NetworkManager(),
    );
  });

  test('网络可用时,getTodo返回真实接口的Todo列表', () async {
    final todos = await repository.getTodo();
    
    expect(todos.isNotEmpty, true);
    // 验证返回数据的结构符合预期
    expect(todos.first.id, isA<int>());
    expect(todos.first.todo, isA<String>());
    expect(todos.first.completed, isA<bool>());
  });

  test('网络不可用时,getTodo返回预设的 fallback 数据', () async {
    // 自定义NetworkManager子类模拟网络不可用
    class OfflineNetworkManager extends NetworkManager {
      @override
      bool isNetworkAvailable() => false;
    }

    final offlineRepo = Repository(
      dio: dio,
      networkManager: OfflineNetworkManager(),
    );

    final todos = await offlineRepo.getTodo();
    
    expect(todos.length, 1);
    expect(todos.first.id, 999);
    expect(todos.first.todo, "FailedTODO");
  });
}

注意:这类测试依赖真实网络和外部接口稳定性,更偏向集成测试,如果接口返回结构变化或网络中断,测试会失败。

二、为什么需要使用Mockito?

Mockito是Mock框架,核心作用是解决单元测试的依赖隔离问题,具体包括:

  • 隔离外部依赖:单元测试只需要验证当前类的逻辑,Mockito可以Mock掉Dio、NetworkManager等外部组件,避免真实网络、第三方服务的状态变化干扰测试结果。
  • 轻松模拟异常场景:可以快速复现网络请求失败、超时、返回错误码,或者NetworkManager返回网络不可用等场景,这些在真实环境中很难稳定触发。
  • 提升测试速度:Mock调用不需要真实网络请求,测试执行效率更高,适合频繁运行的单元测试套件。
  • 突破外部依赖限制:如果真实接口有调用频率限制、需要授权,或者尚未开发完成,Mock可以让测试提前进行。

Mockito测试示例(简单版)

import 'package:dio/dio.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

// 生成Mock类
class MockDio extends Mock implements Dio {}
class MockNetworkManager extends Mock implements NetworkManager {}

void main() {
  late Repository repository;
  late MockDio mockDio;
  late MockNetworkManager mockNetworkManager;

  setUp(() {
    mockDio = MockDio();
    mockNetworkManager = MockNetworkManager();
    repository = Repository(dio: mockDio, networkManager: mockNetworkManager);
  });

  test('网络可用且请求成功时,正确解析返回数据', () async {
    // 模拟网络可用
    when(mockNetworkManager.isNetworkAvailable()).thenReturn(true);
    // 模拟Dio返回的响应数据
    when(mockDio.get('https://dummyjson.com/todos')).thenAnswer((_) async => Response(
      data: {
        'todos': [{'id': 1, 'todo': 'Test Todo', 'completed': false, 'userId': 1}]
      },
      statusCode: 200,
      requestOptions: RequestOptions(path: ''),
    ));

    final todos = await repository.getTodo();

    expect(todos.length, 1);
    expect(todos.first.todo, 'Test Todo');
    // 验证Dio的get方法被正确调用
    verify(mockDio.get('https://dummyjson.com/todos')).called(1);
  });

  test('网络不可用时,返回fallback数据且不调用Dio', () async {
    when(mockNetworkManager.isNetworkAvailable()).thenReturn(false);

    final todos = await repository.getTodo();

    expect(todos.first.id, 999);
    // 验证Dio的get方法从未被调用
    verifyNever(mockDio.get(any));
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:23:19