5月28日 02:46

Tauri 应用如何进行单元测试和集成测试?

Tauri 的测试分三层:Rust 单元测试(纯函数 + MockRuntime)、Rust 集成测试(tests/ 目录)、前端 E2E 测试(WebDriver 或 Vitest)。关键思路是把业务逻辑从 Tauri 命令中抽出来,命令层只做薄薄的包装,这样大部分逻辑用普通 Rust 测试就能覆盖,不需要启动 Tauri 运行时。

追问

单元测试怎么写?需要启动 Tauri 运行时吗?

不需要。正确做法是把业务逻辑提取成纯函数,Tauri 命令只负责调用:

rust
// 业务逻辑:纯函数,不依赖 Tauri pub fn validate_username(name: &str) -> Result<(), String> { if name.is_empty() { return Err("用户名不能为空".into()); } Ok(()) } // Tauri 命令:只做调用包装 #[tauri::command] fn check_username(name: String) -> Result<(), String> { validate_username(&name) }

测试直接测纯函数:

rust
#[cfg(test)] mod tests { use super::*; #[test] fn rejects_empty_username() { assert!(validate_username("").is_err()); } }

如果命令里必须用到 AppHandleState,用 Tauri 提供的 mock_builder

rust
#[cfg(test)] mod tests { use super::*; use tauri::test::{mock_builder, mock_context, noop_assets}; #[test] fn test_with_state() { let app = mock_builder() .manage("test_data".to_string()) .build(mock_context(noop_assets())) .unwrap(); assert_eq!(app.state::<String>().inner(), "test_data"); } }

集成测试怎么组织?

集成测试放在项目根目录的 tests/ 文件夹下,cargo test 会自动发现并运行。和单元测试的区别是:集成测试只能访问 crate 的公开 API,更接近真实使用场景。

rust
// tests/integration_test.rs use my_app::database::ConnectionPool; use my_app::services::UserService; #[tokio::test] async fn test_create_user() { let pool = ConnectionPool::new(":memory:").await.unwrap(); let service = UserService::new(pool); let result = service.create("alice").await; assert!(result.is_ok()); }

要点:用内存数据库(:memory:)替代真实数据库,测试结束自动销毁,不污染环境。

Windows 上 cargo test 报 STATUS_ENTRYPOINT_NOT_FOUND 怎么办?

这是 Tauri 的已知问题。原因是 tauri-winres 只把 Windows manifest 链接到主二进制文件,测试二进制没拿到 manifest,导致 ComCtl6 入口点找不到。

解决方案:在 .cargo/config.toml 里加环境变量:

toml
[env] __TAURI_WORKSPACE__ = "true"

或者更彻底的做法——让测试不依赖 Tauri 运行时,用 #[cfg(not(test))] 隔离需要运行时的模块:

rust
#[cfg(not(test))] mod state; // 依赖 AppHandle,测试时不编译

前端怎么测试 Tauri 的 invoke 调用?

Tauri 提供了 @tauri-apps/api/mocks 里的 mockIPC 来拦截前端对后端的调用:

javascript
import { mockIPC, clearMocks } from "@tauri-apps/api/mocks"; import { invoke } from "@tauri-apps/api/core"; afterEach(() => clearMocks()); test("invoke greet command", async () => { mockIPC((cmd, args) => { if (cmd === "greet") return `Hello, ${args.name}!`; }); const result = await invoke("greet", { name: "World" }); expect(result).toBe("Hello, World!"); });

注意:Vitest + jsdom 环境下需要补 WebCrypto polyfill,否则 Tauri API 会报错:

javascript
import { randomFillSync } from "crypto"; beforeAll(() => { Object.defineProperty(window, "crypto", { value: { getRandomValues: (buf) => randomFillSync(buf) }, }); });

E2E 测试选 WebDriver 还是 Vitest?

WebDriver 是 Tauri 官方方案,但 macOS 桌面端没有 WebDriver 客户端,跑不了。2026 年社区出现了用 Vitest 自定义浏览器提供者(Custom Browser Provider)的方案:启动一个 Tauri 应用加载测试页面,Vitest 在里面跑断言。这个方案全平台可用,还能用 --watch 模式热跑测试、挂 LLDB 调 Rust 代码。项目不复杂的话 WebDriver 够用,如果要做插件级测试或者跨平台 CI,Vitest 方案更灵活。

写段代码

rust
// 完整的 Tauri 命令单元测试示例 #[tauri::command] fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn add_works() { assert_eq!(add(2, 3), 5); assert_eq!(add(-1, 1), 0); } }
标签:Tauri