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

Flutter用Mockito模拟Riverpod Provider报错,求无改真实类的测试方案

Flutter中Mockito模拟Riverpod Provider的测试问题解决

问题场景

在测试Riverpod的TodoList Notifier时,尝试用Mockito模拟依赖的userItemProvider,先后遇到两个报错:

  1. NoSuchMethodError: Class 'MockUserItem' has no instance method '_setElement'
  2. 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();
  });
}

错误原因解析

  1. _setElement方法缺失:Riverpod的Notifier内部包含_setElement等私有初始化方法,Mockito自动生成的Mock类未实现这些方法,导致Provider初始化失败。
  2. Null类型转换错误:自定义Mock类继承AutoDisposeNotifier但未实现build方法,默认返回Null,不符合UserItem要求的非Null返回值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 05:22:11