use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
thread_local! {
#[allow(clippy::type_complexity)]
static FUNCTION_MOCKS: RefCell<HashMap<String, Arc<Mutex<Box<dyn std::any::Any + Send>>>>> = RefCell::new(HashMap::new());
}
pub fn mock_function<F: 'static + Send>(name: &str, mock_fn: F) {
FUNCTION_MOCKS.with(|mocks| {
mocks
.borrow_mut()
.insert(name.to_string(), Arc::new(Mutex::new(Box::new(mock_fn))));
});
}
pub fn get_mock<F>(name: &str) -> Option<F>
where
F: Clone + 'static,
{
FUNCTION_MOCKS.with(|mocks| {
mocks.borrow().get(name).and_then(|mock| {
let guard = mock.lock().unwrap();
let any_ref = &**guard;
(any_ref as &dyn std::any::Any).downcast_ref::<F>().cloned()
})
})
}
pub fn is_mocked(name: &str) -> bool {
FUNCTION_MOCKS.with(|mocks| mocks.borrow().contains_key(name))
}
pub fn clear_mock(name: &str) {
FUNCTION_MOCKS.with(|mocks| {
mocks.borrow_mut().remove(name);
});
}
pub fn clear_all_mocks() {
FUNCTION_MOCKS.with(|mocks| {
mocks.borrow_mut().clear();
});
}
pub fn with_mock<F, R, T>(function_name: &str, mock_fn: F, test_fn: T) -> R
where
F: 'static + Send,
T: FnOnce() -> R,
{
mock_function(function_name, mock_fn);
let result = test_fn();
clear_mock(function_name);
result
}
#[derive(Debug, Default)]
pub struct MockRegistry {
mocks: HashMap<String, usize>, }
impl MockRegistry {
pub fn new() -> Self {
Self {
mocks: HashMap::new(),
}
}
pub fn record_call(&mut self, function_name: &str) {
*self.mocks.entry(function_name.to_string()).or_insert(0) += 1;
}
pub fn call_count(&self, function_name: &str) -> usize {
self.mocks.get(function_name).copied().unwrap_or(0)
}
pub fn was_called(&self, function_name: &str) -> bool {
self.call_count(function_name) > 0
}
pub fn verify_called_times(&self, function_name: &str, expected_count: usize) {
let actual = self.call_count(function_name);
if actual != expected_count {
panic!(
"Expected {} to be called {} times, but it was called {} times",
function_name, expected_count, actual
);
}
}
pub fn reset(&mut self) {
self.mocks.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_function() {
mock_function("test_func", || 100);
assert!(is_mocked("test_func"));
clear_mock("test_func");
assert!(!is_mocked("test_func"));
}
#[test]
fn test_is_mocked() {
assert!(!is_mocked("nonexistent"));
mock_function("exists", || 1);
assert!(is_mocked("exists"));
clear_mock("exists");
assert!(!is_mocked("exists"));
}
#[test]
fn test_clear_all_mocks() {
mock_function("func1", || 1);
mock_function("func2", || 2);
assert!(is_mocked("func1"));
assert!(is_mocked("func2"));
clear_all_mocks();
assert!(!is_mocked("func1"));
assert!(!is_mocked("func2"));
}
#[test]
fn test_with_mock_cleanup() {
with_mock(
"test_function",
|| 999,
|| {
},
);
assert!(!is_mocked("test_function")); }
#[test]
fn test_mock_registry() {
let mut registry = MockRegistry::new();
registry.record_call("func1");
registry.record_call("func1");
registry.record_call("func2");
assert_eq!(registry.call_count("func1"), 2);
assert_eq!(registry.call_count("func2"), 1);
assert!(registry.was_called("func1"));
assert!(!registry.was_called("func3"));
}
#[test]
fn test_verify_called_times_success() {
let mut registry = MockRegistry::new();
registry.record_call("func");
registry.record_call("func");
registry.verify_called_times("func", 2); }
#[test]
#[should_panic(expected = "Expected func to be called 3 times")]
fn test_verify_called_times_failure() {
let mut registry = MockRegistry::new();
registry.record_call("func");
registry.record_call("func");
registry.verify_called_times("func", 3); }
#[test]
fn test_registry_reset() {
let mut registry = MockRegistry::new();
registry.record_call("func");
assert_eq!(registry.call_count("func"), 1);
registry.reset();
assert_eq!(registry.call_count("func"), 0);
}
}