7.6 KiB
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
tokiofor async testing if needed later
Assertion Library:
- Standard
assert!()andassert_eq!()macros from standard library - Can add
pretty_assertionscrate for better failure output if desired
Run Commands:
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:
# Using cargo-watch (install with: cargo install cargo-watch)
cargo watch -x test # Re-run tests on file changes
Coverage:
# 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.rsor 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:
#[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:
// 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
mockitocrate for HTTP mocking - Use
mockallcrate for trait-based mocking - Use
proptestcrate 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:
#[cfg(test)]
mod tests {
struct MockDataStore {
data: Vec<String>,
}
impl DataStore for MockDataStore {
fn fetch(&self, id: u32) -> Option<String> {
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:
#[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:
# 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:
/// 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 withtokiocrate
Example (future reference):
#[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:
#[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:
#[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:
// 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