Flutter SQLite数据库API单元测试报错:无法加载预填充数据库资产
在为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

