如何为使用SQLite数据库的Tauri应用编写单元测试?
解决方案:Tauri + Vitest 测试 SQLite 交互逻辑
针对你遇到的Vitest无法直接访问Tauri Rust侧SQL操作的问题,以下是可行的解决方案,包括对你两种思路的优化和其他替代方案:
优化你的现有思路
1. 启动传参的测试方案
不用在前端代码里直接调用describe/test,而是通过Tauri的条件编译+官方测试命令实现:
- 在Rust的
main.rs中检测命令行参数,比如--test-db,如果存在则启用测试模式,初始化测试数据库并暴露测试接口; - 生产环境通过条件编译标记(
#[cfg(not(test))])排除测试相关代码,确保生产包干净; - 使用Tauri官方提供的
tauri test命令,配合Vitest的tauri环境配置,直接启动测试模式的Tauri实例运行前端测试,不会丢失上下文:
// vitest.config.ts export default defineConfig({ test: { environment: 'tauri', }, })
2. 抽象接口适配不同SQL客户端
这个依赖倒置的思路非常实用,目前没有现成的通用包装类,但实现起来很简单:
首先定义抽象接口,包含你用到的所有SQL操作方法:
export interface DatabaseClient { execute(sql: string, params?: any[]): Promise<number>; select<T>(sql: string, params?: any[]): Promise<T[]>; close(): Promise<void>; }
然后实现Tauri版本的客户端:
import { Database } from '@tauri-apps/plugin-sql'; export class TauriSqlClient implements DatabaseClient { private db: Database | null = null; async init(connectionString: string) { this.db = await Database.load(connectionString); } async execute(sql: string, params?: any[]): Promise<number> { if (!this.db) throw new Error('Database not initialized'); return this.db.execute(sql, params); } async select<T>(sql: string, params?: any[]): Promise<T[]> { if (!this.db) throw new Error('Database not initialized'); return this.db.select<T>(sql, params); } async close(): Promise<void> { if (this.db) await this.db.close(); } }
再实现Node sqlite3版本的客户端(以better-sqlite3为例):
import Database from 'better-sqlite3'; export class NodeSqlClient implements DatabaseClient { private db: Database | null = null; async init(connectionString: string) { this.db = new Database(connectionString.includes(':memory:') ? ':memory:' : connectionString); } async execute(sql: string, params?: any[]): Promise<number> { if (!this.db) throw new Error('Database not initialized'); const stmt = this.db.prepare(sql); return stmt.run(params || []).changes; } async select<T>(sql: string, params?: any[]): Promise<T[]> { if (!this.db) throw new Error('Database not initialized'); const stmt = this.db.prepare(sql); return stmt.all(params || []) as T[]; } async close(): Promise<void> { if (this.db) this.db.close(); } }
测试时注入NodeSqlClient,生产时注入TauriSqlClient,业务逻辑完全不需要关心底层实现。
其他可行方案
1. 直接在Rust侧写SQL测试
把核心的SQL操作逻辑封装到Rust的函数或模块中,用Rust的cargo test直接测试这些逻辑,比如:
#[cfg(test)] mod tests { use super::*; use tauri_plugin_sql::Sql; #[test] fn test_insert_and_query() { let sql = Sql::default(); let db = sql.load("sqlite::memory:").unwrap(); db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)", []).unwrap(); db.execute("INSERT INTO test (name) VALUES (?)", ["test"]).unwrap(); let rows: Vec<(i32, String)> = db.select("SELECT id, name FROM test", []).unwrap(); assert_eq!(rows.len(), 1); assert_eq!(rows[0].1, "test"); } }
前端只需要测试调用这些Rust函数的逻辑,用@tauri-apps/api/mocks模拟API即可。
2. 使用内存数据库加速测试
不管是用Tauri的sqlite:memory:还是Node的内存数据库,在测试时使用内存实例,避免操作真实文件,测试速度更快,且不会留下测试数据。配合Vitest的钩子函数初始化:
let client: DatabaseClient; beforeEach(async () => { client = new NodeSqlClient(); // 测试用Node客户端 await client.init('sqlite::memory:'); // 初始化测试表结构 await client.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)'); }); afterEach(async () => { await client.close(); });
内容的提问来源于stack exchange,提问作者Frank Müller
相关产品推荐
相关产品推荐

