Testing
Unit Testing
Testing individual services, guards, and interceptors in isolation.
Unit tests in Toni are straightforward Rust unit tests — no special testing framework is needed. Since services are plain structs with methods, you instantiate them directly and call their methods.
Testing a service
#[injectable(pub struct MathService {})]
impl MathService {
pub fn new() -> Self { Self {} }
pub fn add(&self, a: i32, b: i32) -> i32 { a + b }
pub fn divide(&self, a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
let service = MathService::new();
assert_eq!(service.add(2, 3), 5);
assert_eq!(service.add(-1, 1), 0);
}
#[test]
fn test_divide_by_zero() {
let service = MathService::new();
assert_eq!(service.divide(10.0, 0.0), None);
}
#[test]
fn test_divide() {
let service = MathService::new();
assert_eq!(service.divide(10.0, 2.0), Some(5.0));
}
}Testing services with dependencies
Construct dependencies manually for unit tests:
#[cfg(test)]
mod tests {
use super::*;
fn make_orders_service() -> OrdersService {
// Construct with real (or mock) dependencies
let users_service = UsersService::new();
let payments_service = PaymentsService::new();
OrdersService::new(users_service, payments_service)
}
#[tokio::test]
async fn test_create_order() {
let service = make_orders_service();
let result = service.create_order(1, 99.99).await;
assert!(result.is_ok());
}
}Testing guards
#[cfg(test)]
mod tests {
use super::*;
use toni::HttpRequest;
fn make_request_with_token(token: &str) -> HttpRequest {
let mut req = HttpRequest::new();
req.headers.insert(
"authorization".to_string(),
format!("Bearer {token}")
);
req
}
#[test]
fn test_auth_guard_allows_valid_token() {
let guard = AuthGuard;
let req = make_request_with_token("valid-token-123");
let ctx = Context::from_request(req);
assert!(guard.can_activate(&ctx));
}
#[test]
fn test_auth_guard_blocks_missing_token() {
let guard = AuthGuard;
let ctx = Context::from_request(HttpRequest::new());
assert!(!guard.can_activate(&ctx));
}
}Testing async services
Use #[tokio::test] for async tests:
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_database_service() {
let service = DatabaseService::new();
// on_module_init won't run automatically in unit tests
// Call it manually if needed:
// service.connect().await;
let users = service.find_all().await;
assert!(!users.is_empty());
}
}Mocking dependencies
For true isolation, create mock implementations of your traits. The mockall crate is popular for this:
[dev-dependencies]
mockall = "0.13"use mockall::mock;
mock! {
UserRepository {}
impl UserRepository {
fn find_by_id(&self, id: u32) -> Option<User>;
fn save(&self, user: &User) -> Result<(), String>;
}
}
#[test]
fn test_with_mock() {
let mut mock_repo = MockUserRepository::new();
mock_repo.expect_find_by_id()
.with(mockall::predicate::eq(1u32))
.returning(|_| Some(User { id: 1, name: "Alice".into() }));
let service = UserService::with_repository(mock_repo);
let user = service.find(1);
assert_eq!(user.unwrap().name, "Alice");
}