Testing
This guide covers running and writing tests for job-orchestrator.
Running Tests
All Tests
cargo test
With Output
See println! output from tests:
cargo test -- --nocapture
Specific Test
# By name
cargo test test_upload
# By module
cargo test orchestrator::tests
Ignored Tests
Some tests may be ignored by default (slow, require setup):
cargo test -- --ignored
Test Coverage
Using cargo-tarpaulin
Install:
cargo install cargo-tarpaulin
Generate coverage:
# HTML report
cargo tarpaulin --out Html --output-dir ./coverage
# XML report (for CI)
cargo tarpaulin --out Xml --output-dir ./coverage
View report:
open coverage/tarpaulin-report.html
Test Structure
Tests are organized alongside the code they test:
src/
├── lib.rs
├── orchestrator/
│ ├── mod.rs
│ └── tests.rs # Orchestrator tests
├── client/
│ ├── mod.rs
│ └── tests.rs # Client tests
└── utils/
└── mod.rs # Inline tests with #[cfg(test)]
Writing Tests
Unit Tests
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_something() {
let result = function_under_test();
assert_eq!(result, expected_value);
}
}
}
Async Tests
Use tokio::test for async functions:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
}
}
Integration Tests
Create files in tests/ directory:
#![allow(unused)]
fn main() {
// tests/integration_test.rs
use job_orchestrator::*;
#[tokio::test]
async fn test_full_workflow() {
// Setup
// Test
// Verify
}
}
Mocking
Using mockall
For trait-based mocking:
#![allow(unused)]
fn main() {
use mockall::automock;
#[automock]
trait Database {
fn get(&self, id: i32) -> Option<Job>;
}
#[test]
fn test_with_mock() {
let mut mock = MockDatabase::new();
mock.expect_get()
.with(eq(1))
.returning(|_| Some(Job::default()));
// Use mock in test
}
}
Using mockito
For HTTP mocking:
#![allow(unused)]
fn main() {
use mockito::Server;
#[tokio::test]
async fn test_http_client() {
let mut server = Server::new();
let mock = server.mock("GET", "/health")
.with_status(200)
.create();
// Test against server.url()
mock.assert();
}
}
Test Utilities
Test Fixtures
Create reusable test data:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod test_utils {
pub fn create_test_job() -> Job {
Job {
id: 1,
user_id: 1,
service: "test".to_string(),
status: Status::Queued,
..Default::default()
}
}
}
}
Temporary Directories
Use tempfile for temporary test directories:
#![allow(unused)]
fn main() {
use tempfile::TempDir;
#[test]
fn test_file_operations() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
// Test file operations
// TempDir is automatically cleaned up
}
}
CI Testing
Tests run automatically on GitHub Actions:
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test
Linting
Clippy
Run Clippy for additional checks:
cargo clippy -- -D warnings
Common fixes:
#[allow(clippy::lint_name)]to suppress specific lints- Configure in
clippy.tomlorCargo.toml
Formatting
Check formatting:
cargo fmt -- --check
Fix formatting:
cargo fmt
Debugging Tests
With println
#![allow(unused)]
fn main() {
#[test]
fn test_debug() {
let value = compute_something();
println!("Debug value: {:?}", value); // Use --nocapture to see
assert!(value.is_valid());
}
}
With RUST_BACKTRACE
RUST_BACKTRACE=1 cargo test