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

Flutter测试报错:type 'Null' is not a subtype of type 'Future<AppColors>'

问题:测试Provider加载SharedPreferences数据时的类型错误

我正在学习Provider,想测试从SharedPreferences加载颜色数据的逻辑,但运行测试时报错,试过Stack Overflow和AI工具都没解决。我的应用是调色板生成器,有负责加载更新颜色的Provider类,还有处理SharedPreferences存储的ThemeRepository。

报错信息

type 'Null' is not a subtype of type 'Future<AppColors>'
test/theme_provider_test.dart 7:7    MockThemeRepository.loadThemeColors
test/theme_provider_test.dart 18:24  main

相关代码

测试代码

class MockThemeRepository extends Mock implements ThemeRepository {}
         
            void main() {
             WidgetsFlutterBinding.ensureInitialized();
         
             final themeRepository = MockThemeRepository();
         
             when(themeRepository.loadThemeColors()).thenAnswer((_) async => AppColors(
                primary: Colors.blue,
                accent: Colors.orange,
                background: Colors.white,
                backgroundDark: Colors.grey,
                error: Colors.red,
              ));
         
            test('Test if the theme colors are loaded from SharedPreferences', () async {
             final loadedColors = await themeRepository.loadThemeColors();
         
             expect(loadedColors.primary, equals(Colors.blue));
             expect(loadedColors.accent, equals(Colors.orange));
             expect(loadedColors.background, equals(Colors.white));
             expect(loadedColors.backgroundDark, equals(Colors.grey));
             expect(loadedColors.error, equals(Colors.red));
        }); 

仓库代码

class ThemeRepository {
            static const String themeColorsKey = 'themeColors';
            final _logger = Logger();
             
            Future<AppColors> loadThemeColors() async {
             try {
              final prefs = await SharedPreferences.getInstance();
              final encodedData = prefs.getString(themeColorsKey);
              if (encodedData != null) {
                final colorsData = json.decode(encodedData);
                return AppColors.fromJson(colorsData);
               }
              } catch (error, stackTrace) {
              _logger.e('Falha ao carregar as cores do tema', error, stackTrace);
             }
             // Retorna cores padrão em caso de falha ou ausência de cores salvas
             return AppColors.defaultColors();
         }
        }

Provider代码

ThemeProvider(
        this.themeData,
        this.currentThemeColors,
      );
     
      Future<void> loadThemeColors() async {
        try {
          final appColors = await repository.loadThemeColors();
     
          updateThemeFromColors(appColors);
        } catch (error, stackTrace) {
          _logger.e('Falha ao carregar as cores do tema', error, stackTrace);
        }
      }

    void updateThemeFromColors(AppColors colors) {
        final newTheme = themeData.copyWith(
          colorScheme: themeData.colorScheme.copyWith(
            primary: colors.primary,
            secondary: colors.accent,
            background: colors.background,
            error: colors.error,
          ),
          primaryColor: colors.primary,
          appBarTheme: themeData.appBarTheme.copyWith(
            backgroundColor: colors.backgroundDark,
          ),
          floatingActionButtonTheme: themeData.floatingActionButtonTheme.copyWith(
            backgroundColor: MaterialStateColor.resolveWith((states) {
              if (states.contains(MaterialState.disabled)) {
                return colors.accent;
              }
              return colors.accent;
            }),
          ),
          scaffoldBackgroundColor: colors.background,
        );
    themeData = newTheme;
    currentThemeColors = colors;
    repository.saveThemeColors(colors);
    notifyListeners();
  }

问题

请问如何正确测试SharedPreferences的调用并使用when方法?


解决方案

错误根源

你的mock方法没有正确生效,调用loadThemeColors()时返回了null,但方法声明要求返回Future<AppColors>,导致类型不匹配报错。大概率是mock库的方法匹配逻辑出了问题,或者测试代码的初始化步骤有误。

修复步骤与示例

1. 修正Mock仓库的测试代码

确保mock方法的签名完全匹配,并且用setUp初始化测试环境,避免重复代码:

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:your_app/models/app_colors.dart';
import 'package:your_app/repositories/theme_repository.dart';
import 'package:flutter/material.dart';

class MockThemeRepository extends Mock implements ThemeRepository {}

void main() {
  late MockThemeRepository themeRepository;

  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
    themeRepository = MockThemeRepository();
  });

  test('测试从Mock仓库加载主题颜色', () async {
    // 明确指定mock方法的返回值,确保是Future<AppColors>类型
    when(themeRepository.loadThemeColors()).thenAnswer((_) async => AppColors(
          primary: Colors.blue,
          accent: Colors.orange,
          background: Colors.white,
          backgroundDark: Colors.grey,
          error: Colors.red,
        ));

    final loadedColors = await themeRepository.loadThemeColors();

    // 断言颜色是否匹配
    expect(loadedColors.primary, Colors.blue);
    expect(loadedColors.accent, Colors.orange);
    expect(loadedColors.background, Colors.white);
    expect(loadedColors.backgroundDark, Colors.grey);
    expect(loadedColors.error, Colors.red);

    // 验证方法确实被调用过一次
    verify(themeRepository.loadThemeColors()).called(1);
  });
}

2. 直接测试ThemeRepository与SharedPreferences的交互

如果要真实测试SharedPreferences的调用,不需要mock整个仓库,用官方提供的mock工具即可:

import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:your_app/models/app_colors.dart';
import 'package:your_app/repositories/theme_repository.dart';
import 'dart:convert';

void main() {
  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
    // 初始化SharedPreferences的mock实例
    SharedPreferences.setMockInitialValues({});
  });

  test('无存储数据时,仓库返回默认颜色', () async {
    final repository = ThemeRepository();
    final colors = await repository.loadThemeColors();

    expect(colors, AppColors.defaultColors());
  });

  test('从SharedPreferences加载已存储的颜色', () async {
    final prefs = await SharedPreferences.getInstance();
    final testColors = AppColors(
      primary: Colors.blue,
      accent: Colors.orange,
      background: Colors.white,
      backgroundDark: Colors.grey,
      error: Colors.red,
    );
    // 先往mock的SharedPreferences存入数据
    await prefs.setString(ThemeRepository.themeColorsKey, json.encode(testColors.toJson()));

    final repository = ThemeRepository();
    final loadedColors = await repository.loadThemeColors();

    // 断言加载的颜色与存入的一致
    expect(loadedColors.primary, testColors.primary);
    expect(loadedColors.accent, testColors.accent);
  });
}

3. 测试Provider的完整逻辑

验证Provider调用仓库加载颜色后,是否正确更新自身状态:

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:your_app/models/app_colors.dart';
import 'package:your_app/providers/theme_provider.dart';
import 'package:your_app/repositories/theme_repository.dart';
import 'package:flutter/material.dart';

class MockThemeRepository extends Mock implements ThemeRepository {}

void main() {
  late MockThemeRepository themeRepository;
  late ThemeProvider themeProvider;
  late ThemeData initialTheme;

  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
    themeRepository = MockThemeRepository();
    initialTheme = ThemeData.light();
    // 初始化Provider,假设你有对应的构造参数
    themeProvider = ThemeProvider(initialTheme, AppColors.defaultColors());
    // 给Provider注入mock仓库
    themeProvider.repository = themeRepository;
  });

  test('Provider加载颜色后正确更新主题状态', () async {
    final testColors = AppColors(
      primary: Colors.blue,
      accent: Colors.orange,
      background: Colors.white,
      backgroundDark: Colors.grey,
      error: Colors.red,
    );

    // mock仓库的加载方法
    when(themeRepository.loadThemeColors()).thenAnswer((_) async => testColors);

    await themeProvider.loadThemeColors();

    // 验证Provider的状态是否更新
    expect(themeProvider.currentThemeColors, testColors);
    expect(themeProvider.themeData.primaryColor, Colors.blue);
    expect(themeProvider.themeData.appBarTheme.backgroundColor, Colors.grey);

    // 验证仓库方法被调用,并且保存方法触发
    verify(themeRepository.loadThemeColors()).called(1);
    verify(themeRepository.saveThemeColors(testColors)).called(1);
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:32:07