# Testing Patterns **Analysis Date:** 2026-02-28 ## Test Framework **Test Runner:** - Rust built-in test framework (via `cargo test`) - No external test framework currently integrated - Can add `tokio` for async testing if needed later **Assertion Library:** - Standard `assert!()` and `assert_eq!()` macros from standard library - Can add `pretty_assertions` crate for better failure output if desired **Run Commands:** ```bash cargo test # Run all tests cargo test -- --nocapture # Run tests with output visible cargo test -- --test-threads=1 # Run tests sequentially cargo test --lib # Run only library tests cargo test --doc # Run doctests ``` **Watch Mode:** ```bash # Using cargo-watch (install with: cargo install cargo-watch) cargo watch -x test # Re-run tests on file changes ``` **Coverage:** ```bash # Using cargo-tarpaulin (install with: cargo install cargo-tarpaulin) cargo tarpaulin --out Html # Generate HTML coverage report ``` ## Test File Organization **Location - Unit Tests:** - Co-located with implementation: Tests in same file as source code - Tests go in submodule at end of source file **Location - Integration Tests:** - Separate `tests/` directory at workspace root - File: `tests/integration_test.rs` or similar - Each file is compiled as separate crate **Naming:** - Unit test modules: `#[cfg(test)] mod tests` - Integration test files: `integration_test.rs`, `e2e_test.rs` - Test functions: `test_function_name_description()` - Unit tests: `tests::test_parses_valid_input()` **Current Structure:** ``` src/ ├── main.rs # Contains #[cfg(test)] mod tests ├── lib.rs # If created, contains library tests └── ... tests/ ├── integration_test.rs └── ... ``` ## Test Structure **Suite Organization - Unit Test Pattern:** ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_function_does_something() { let input = setup_test_data(); let result = function_under_test(input); assert_eq!(result, expected_value); } } ``` **Suite Organization - Integration Test Pattern:** ```rust // tests/integration_test.rs #[test] fn test_system_integrates_correctly() { let app = App::new(); let result = app.process_command("help"); assert!(result.is_ok()); } ``` **Patterns:** - **Setup:** Create test data or fixtures in test function body - **Teardown:** Not typically needed in Rust; cleanup happens automatically when test scope ends - **Assertion:** Use `assert!()`, `assert_eq!()`, `assert_ne!()` for conditions ## Mocking **Framework:** No explicit mocking framework currently integrated **If mocking becomes necessary:** - Use `mockito` crate for HTTP mocking - Use `mockall` crate for trait-based mocking - Use `proptest` crate for property-based testing **Current Approach:** - Manual test doubles: Create simplified implementations of traits for testing - Dependency injection: Pass different implementations at test time - Stub data: Return hardcoded test data instead of external calls **Example of Manual Test Double:** ```rust #[cfg(test)] mod tests { struct MockDataStore { data: Vec, } impl DataStore for MockDataStore { fn fetch(&self, id: u32) -> Option { self.data.get(id as usize).cloned() } } #[test] fn test_with_mock() { let mock = MockDataStore { data: vec!["test".to_string()] }; let result = process_data(&mock); assert!(result.is_ok()); } } ``` **What to Mock:** - External service calls (network, file I/O) - Database operations - Time-dependent behavior - Hardware-dependent code **What NOT to Mock:** - Core business logic - Standard library functions - Tested utility functions - Pure functions ## Fixtures and Factories **Test Data:** - Currently: Manual test data created in test functions - Pattern: Helper functions to build test objects **Example:** ```rust #[cfg(test)] mod tests { fn create_test_user() -> User { User { id: 1, name: "Test User".to_string(), email: "test@example.com".to_string(), } } #[test] fn test_user_creation() { let user = create_test_user(); assert_eq!(user.name, "Test User"); } } ``` **Location:** - Helper functions in same module as tests: `#[cfg(test)] mod tests` - If many fixtures needed: Create `src/test_utils.rs` (not included in release build via `#[cfg(test)]`) ## Coverage **Requirements:** Not currently enforced **To Enable Coverage Tracking:** ```bash # Using llvm-cov (install with: cargo install cargo-llvm-cov) cargo llvm-cov # Generate coverage report cargo llvm-cov --html # HTML report in target/llvm-cov/html ``` **Target for new code:** - Aim for 80%+ coverage on public APIs - 100% coverage on critical paths - Document uncovered code with `#[cfg(test)]` or comments ## Test Types **Unit Tests:** - Scope: Single function or small module in isolation - Approach: Test behavior with various inputs including edge cases - Location: Same file as implementation in `#[cfg(test)] mod tests` - Tools: Standard library assertions **Integration Tests:** - Scope: Multiple components working together - Approach: Test workflows and interactions between modules - Location: `tests/` directory at workspace root - Tools: Standard library assertions **Doc Tests:** - Scope: Examples in `///` doc comments - Approach: Verify example code actually works - Location: Documentation strings in source - Tools: Built-in `cargo test --doc` **E2E Tests:** - Currently: Not applicable (TUI application, no external API) - When applicable: Test complete user workflows end-to-end **Example Doc Test:** ```rust /// Calculate the sum of two numbers. /// /// # Examples /// /// ``` /// assert_eq!(add(2, 2), 4); /// ``` pub fn add(a: i32, b: i32) -> i32 { a + b } ``` ## Common Patterns **Async Testing:** - Not currently used (no async in codebase) - When needed: Use `#[tokio::test]` attribute with `tokio` crate **Example (future reference):** ```rust #[tokio::test] async fn test_async_function() { let result = async_operation().await; assert!(result.is_ok()); } ``` **Error Testing:** - Test both success and failure paths - Use `.is_err()` or `.is_ok()` to test Result types - Use `.is_some()` or `.is_none()` to test Option types **Example:** ```rust #[test] fn test_handles_invalid_input() { let result = parse_config("invalid"); assert!(result.is_err()); } #[test] fn test_handles_valid_input() { let result = parse_config("valid"); assert!(result.is_ok()); } ``` **Property-Based Testing (future):** If using `proptest`: ```rust #[test] fn test_reverse_is_involutive(input in ".*") { let reversed = reverse(&input); let double_reversed = reverse(&reversed); assert_eq!(input, double_reversed); } ``` ## Test Organization Best Practices **File Structure:** ``` src/ ├── main.rs ├── ui.rs # Contains #[cfg(test)] mod tests at bottom ├── handler.rs # Contains #[cfg(test)] mod tests at bottom └── model.rs # Contains #[cfg(test)] mod tests at bottom tests/ ├── integration_tests.rs # Full-system integration tests └── fixtures/ # Test data files if needed ``` **Test Module Template:** ```rust // At end of source file #[cfg(test)] mod tests { use super::*; // Setup helpers fn create_test_fixture() -> TestData { // ... } // Tests organized by function mod function_name_tests { use super::*; #[test] fn test_success_case() { // ... } #[test] fn test_error_case() { // ... } } } ``` --- *Testing analysis: 2026-02-28*