This document outlines testing guidelines for Navius components, with special focus on testing complex features like the Two-Tier Cache implementation.
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_user_service_with_cache() {
let app = test_app().await;
// Create a userlet user_id = app.create_user("test@example.com").await.unwrap();
// First request should hit the databaselet start = Instant::now();
let user1 = app.get_user(user_id).await.unwrap();
let first_request_time = start.elapsed();
// Second request should hit the cachelet start = Instant::now();
let user2 = app.get_user(user_id).await.unwrap();
let second_request_time = start.elapsed();
// Verify cache is fasterassert!(second_request_time < first_request_time);
// Verify data is the sameassert_eq!(user1, user2);
}
}
#![allow(unused)]fnmain() {
#[test]fntest_cache_ttl() {
// Arrangelet mock_clock = MockClock::new();
let cache = InMemoryCache::new_with_clock(100, mock_clock.clone());
// Act - Set a value with TTL
cache.set("key", b"value", Some(Duration::from_secs(5))).unwrap();
// Assert - Value exists before expirationassert_eq!(cache.get("key").unwrap(), b"value");
// Act - Advance time past TTL
mock_clock.advance(Duration::from_secs(6));
// Assert - Value is gone after expirationassert!(cache.get("key").is_err());
}
}
Testing caching components requires special attention to:
Cache Hit/Miss Scenarios
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_cache_hit_miss() {
let cache = create_test_cache().await;
// Test cache misslet result = cache.get("missing-key").await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), AppError::NotFound { .. }));
// Set value and test cache hit
cache.set("test-key", b"value", None).await.unwrap();
let result = cache.get("test-key").await.unwrap();
assert_eq!(result, b"value");
}
}
TTL Behavior
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_cache_ttl() {
let cache = create_test_cache().await;
// Set with short TTL
cache.set("expires", b"value", Some(Duration::from_millis(100))).await.unwrap();
// Verify existslet result = cache.get("expires").await.unwrap();
assert_eq!(result, b"value");
// Wait for expiration
tokio::time::sleep(Duration::from_millis(150)).await;
// Verify expiredlet result = cache.get("expires").await;
assert!(result.is_err());
}
}
Two-Tier Cache Promotion
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_two_tier_promotion() {
let fast_cache = MockCache::new("fast");
let slow_cache = MockCache::new("slow");
// Configure mocks
fast_cache.expect_get().with(eq("key")).return_error(AppError::not_found("key"));
slow_cache.expect_get().with(eq("key")).return_once(|_| Ok(b"value".to_vec()));
fast_cache.expect_set().with(eq("key"), eq(b"value".to_vec()), any()).return_once(|_, _, _| Ok(()));
let two_tier = TwoTierCache::new(
Box::new(fast_cache),
Box::new(slow_cache),
true, // promote_on_getNone,
None,
);
// Item should be fetched from slow cache and promoted to fast cachelet result = two_tier.get("key").await.unwrap();
assert_eq!(result, b"value");
}
}
Redis Unavailability
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_redis_unavailable() {
let config = CacheConfig {
redis_url: "redis://nonexistent:6379",
// other config...
};
// Create cache with invalid Redis URLlet cache = create_memory_only_two_tier_cache(&config, None).await;
// Should still work using just the memory cache
cache.set("test", b"value", None).await.unwrap();
let result = cache.get("test").await.unwrap();
assert_eq!(result, b"value");
}
}
Concurrent Operations
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_concurrent_operations() {
let cache = create_test_cache().await;
// Spawn multiple tasks writing to the same keyletmut handles = vec![];
for i in0..10 {
let cache_clone = cache.clone();
let handle = tokio::spawn(asyncmove {
let value = format!("value-{}", i).into_bytes();
cache_clone.set("concurrent-key", value, None).await.unwrap();
});
handles.push(handle);
}
// Wait for all operations to completefor handle in handles {
handle.await.unwrap();
}
// Verify key existslet result = cache.get("concurrent-key").await;
assert!(result.is_ok());
}
}
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_cache_error_handling() {
let cache = create_test_cache().await;
// Test missing keylet result = cache.get("nonexistent").await;
assert!(result.is_err());
// Test invalid serializationlet typed_cache = cache.get_typed_cache::<User>();
let result = typed_cache.get("invalid-json").await;
assert!(result.is_err());
}
}
#![allow(unused)]fnmain() {
#[tokio::test]asyncfntest_with_real_redis() {
// Skip if Redis is not availableif !is_redis_available("redis://localhost:6379").await {
println!("Skipping test: Redis not available");
return;
}
let config = CacheConfig {
redis_url: "redis://localhost:6379".to_string(),
// other config...
};
let cache = create_two_tier_cache(&config, None).await.unwrap();
// Test with real Redis...
}
}