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

Flutter SQLite数据库API单元测试报错:无法加载预填充数据库资产

解决Flutter单元测试中预填充SQLite数据库加载失败问题

在为Flutter SQLite数据库API编写单元测试时,使用assets/database/database.db作为预填充数据库,正常构建运行项目无异常,但执行测试时抛出错误:

Exception: Error copying database: Unable to load asset: "assets\database\database.db".
The asset does not exist or has empty data.
package:drinking_game/db_api/new_database_api.dart 49:9 DatabaseAPI._initDB

核心原因是单元测试环境默认不会自动加载Flutter资源,rootBundle无法正常访问assets目录下的文件,以下是三种可行解决方案:

方案一:测试中手动复制数据库文件

在测试的setUp阶段,直接将项目中的预填充数据库复制到测试环境的数据库路径下,绕过rootBundle的限制:

修改database_api_test.dart:

import 'package:flutter_test/flutter_test.dart';
import 'package:drinking_game/db_api/database_api.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'dart:io';
import 'package:path/path.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  databaseFactory = databaseFactoryFfi;

  setUp(() async {
    // 获取测试环境的数据库存储路径
    final dbTargetPath = join(await getDatabasesPath(), "database.db");
    final targetFile = File(dbTargetPath);
    
    if (!await targetFile.exists()) {
      // 从项目根目录的assets文件夹复制数据库文件
      final sourceFile = File('assets/database/database.db');
      await sourceFile.copy(dbTargetPath);
    }
  });

  test('fetchLanguage returns correct data', () async {
    var api = DatabaseAPI();
    var lang = await api.fetchLanguage();
    expect(lang, "Italiano");
  });
}

方案二:在DatabaseAPI中适配测试环境

通过判断环境变量,让_initDB方法在测试时直接读取本地文件,而非通过rootBundle:

修改database_api.dart中的_initDB方法:

Future<Database> _initDB() async {
  final path = join(await getDatabasesPath(), _dbName);
  final exist = await databaseExists(path);
  
  if (!exist) {
    try {
      // 检测是否处于测试环境(Flutter测试会自动设置该环境变量)
      bool isTest = Platform.environment.containsKey('FLUTTER_TEST');
      
      if (isTest) {
        // 测试环境下直接从项目assets目录复制文件
        final sourceFile = File('assets/database/database.db');
        await sourceFile.copy(path);
      } else {
        // 正常运行时使用rootBundle加载资源
        ByteData data = await rootBundle.load(join('assets', 'database', _dbName));
        List<int> bytes = data.buffer.asUint8List();
        await File(path).writeAsBytes(bytes);
      }
    } catch (error) {
      throw Exception('Error copying database: $error');
    }
  }
  return await openDatabase(path);
}

测试文件无需额外修改,保持原有代码即可。

方案三:自定义测试资源加载器

通过自定义TestAssetBundle替换默认的rootBundle,让测试环境可以正常读取assets文件:

修改database_api_test.dart:

import 'package:flutter_test/flutter_test.dart';
import 'package:drinking_game/db_api/database_api.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'dart:io';
import 'package:flutter/services.dart';

// 自定义测试资源加载器
class TestAssetBundle extends CachingAssetBundle {
  @override
  Future<ByteData> load(String key) async {
    final file = File(key);
    if (await file.exists()) {
      return ByteData.view(file.readAsBytesSync().buffer);
    }
    throw FlutterError('Asset not found: $key');
  }
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  // 替换默认的rootBundle为自定义测试资源加载器
  rootBundle = TestAssetBundle();
  databaseFactory = databaseFactoryFfi;

  test('fetchLanguage returns correct data', () async {
    var api = DatabaseAPI();
    var lang = await api.fetchLanguage();
    expect(lang, "Italiano");
  });
}

这种方式可以保留原有DatabaseAPI的实现,无需修改业务代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:14:53