Tauriプロジェクトのテスト戦略は、一般的なRustプロジェクトとは異なる特性を持ちます。最大の問題は、Tauriランタイム(AppHandle、Window、State)をテスト環境에서 直接構築できないことです。本稿では、実用的な階層型テストアプローチを整理します。
一、Tauriテストが特殊な理由
Tauriコマンドは通常 다음과 같이定義されます:
#[tauri::command]
pub async fn create_post(
state: State<'_, AppState>,
payload: CreatePostPayload,
) -> Result<Post, String> {
state.post_service.create(payload).await.map_err(|e| e.to_string())
}
ここで重要なのは、State<'_, AppState> がTauriランタイムによって注入されるものであるため、テストコードから手動で構築することができません。単純に #[test] を記述すると、コンパイルエラーまたはpanicが発生します。
また、Windows環境では cargo test で生成されるバイナリにTauriが必要とするWindows manifestが含まれていないため、STATUS_ENTRYPOINT_NOT_FOUND エラーが発生することです。これは既知のプラットフォーム制限です。
二、階層型テスト戦略
┌─────────────────────────────────┐
│ e2e テスト │ ← WebdriverIO / Selenium
│ Tauri コマンド、emit、ウィンドウ操作 │
├─────────────────────────────────┤
│ 統合テスト │ ← cargo test (tests/ ディレクトリ)
│ データベース、ファイルシステム、Service層 │
├─────────────────────────────────┤
│ ユニットテスト │ ← cargo test (同ファイル mod tests)
│ 純ビジネスロジック、Domain層、ユーティリティ関数 │
└─────────────────────────────────┘
原則:ロジックを分離し、コマンド層は薄く保ちます。
三、ユニットテスト:純ロジックのテスト
ビジネスロジックをコマンドから抽出し、通常の関数として実装します:
// ビジネスロジック:純関数、Tauriに依存しない
pub fn validate_username(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("ユーザー名は空にできません".to_string());
}
if name.len() > 32 {
return Err("ユーザー名は32文字以内にしてください".to_string());
}
Ok(())
}
// Tauriコマンド:呼び出しのみ担当
#[tauri::command]
pub fn check_username(name: String) -> Result<(), String> {
validate_username(&name)
}
テストではビジネスロジックのみを検証します:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_username() {
assert!(validate_username("").is_err());
}
#[test]
fn test_valid_username() {
assert!(validate_username("alice").is_ok());
}
#[test]
fn test_too_long_username() {
let long_name = "a".repeat(33);
assert!(validate_username(&long_name).is_err());
}
}
四、統合テスト:Service/データベースのテスト
メモリデータベース(SQLite :memory:)を使用して統合テストを実行し、Tauriランタイムに依存しません:
// tests/post_service_test.rs
use my_app::infrastructure::database::ConnectionPool;
use my_app::application::services::PostService;
#[tokio::test]
async fn test_create_post() {
// メモリDB使用、テスト終了時に自動解放
let pool = ConnectionPool::new(":memory:").await.unwrap();
pool.run_migrations().await.unwrap();
let service = PostService::new(pool);
let result = service.create("hello world").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().content, "hello world");
}
Cargo.toml で tokio のtest featureを有効にします:
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
五、AppHandleが必要な場合:tauri::testの活用
Tauriは mock_builder を提供しており、AppHandleが必要なシナリオのテストに役立ちます:
# Cargo.toml
[dev-dependencies]
tauri = { version = "2", features = ["test"] }
#[cfg(test)]
mod tests {
use tauri::test::mock_builder;
use tauri::Manager;
#[test]
fn test_state_injection() {
let app = mock_builder()
.build(tauri::generate_context!())
.unwrap();
app.manage(MyState { count: 0 });
let state: tauri::State<MyState> = app.state();
assert_eq!(state.count, 0);
}
}
⚠️ Windows環境では、この種のテストはmanifest問題により失敗する可能性があります。Linux CIで実行することを推奨します。
六、e2eテスト:Tauriコマンド自体のテスト
ランタイム真正に依存する部分(イベントemit、ウィンドウ操作、完全なコマンド呼び出し)には、WebdriverIOを使用して実際のTauriウィンドウを駆動します。
Tauri公式ドキュメント:https://tauri.app/develop/tests/webdriver/
基本的な流れ:
tauri-driverをインストール- WebdriverIOを設定
- Tauriアプリを起動し、WebDriverプロトコルを通じてコマンドを呼び出し、結果をアサーション
cargo install tauri-driver
// wdio.conf.js (簡略化)
export const config = {
specs: ['./tests/e2e/**/*.spec.js'],
capabilities: [{
'tauri:options': {
application: './src-tauri/target/release/my-app'
}
}],
}
七、Windows上の特有の問題
Windowsネイティブ環境で cargo test を実行すると、プロジェクトがTauriランタイムに依存している場合(間接的であっても)、 следующиеエラーが発生します:
error: process didn't exit successfully: STATUS_ENTRYPOINT_NOT_FOUND
根本原因:tao(Tauriのウィンドウライブラリ)が comctl32.dll v6 を静的リンクしており、Windows manifestでアクティブ化する必要がありますが、cargo test で生成されるバイナリにはこのmanifestがありません。
解決策:
| アプローチ | 説明 |
|---|---|
| Linux/Dockerでテストを実行 | 最も安定、CIでの使用を推奨 |
#\[cfg(not(test))\] でTauri依存を分離 |
テストバイナリがTauriランタイムをリンクしないようにする |
| ランタイム非依存の層のみをテスト | 根本的な解決策、アーキテクチャ上で分離 |
プロジェクトでの分離書き込み例:
// lib.rs
#[cfg(not(test))]
mod state; // stateモジュールはAppHandleに依存、テスト時にはコンパイルしない
#[cfg(not(test))]
mod tauri_app;
八、クイックリファレンス
| テスト対象 | 使用する方法 | Windowsで実行可能 |
|---|---|---|
| 純関数、Domainロジック | #\[test\] |
✅ |
| 非同期Service | #\[tokio::test\] |
✅ |
| データベース操作 | #\[tokio::test\] + メモリDB |
✅ |
| AppHandleが必要 | tauri::test::mock\_builder |
⚠️ 不安定 |
| Tauriコマンド、emit、ウィンドウ | WebdriverIO e2e | ✅(ビルドが必要) |
九、まとめ
Tauriテストの中核的なアプローチは、以下の言葉に集約されます:
ビジネスロジックをTauriコマンドから分離し、コMAND層はパラメータ受け渡のみ担当、ロジック層は通常のRustテストでカバー、ランタイム依存部分はe2eで補完する。
これにより、テストカバレッジを確保しながら、Tauriランタイムの制限に惑わされることなく開発を進められます。