Skip to main content

windjammer_runtime/
mock_interface.rs

1//! Advanced interface (trait) mocking utilities
2//!
3//! Provides runtime support for mocking traits/interfaces.
4
5use std::any::Any;
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9/// Method call record
10#[derive(Debug, Clone)]
11pub struct MethodCall {
12    pub method_name: String,
13    pub args: Vec<String>,
14}
15
16impl MethodCall {
17    pub fn new(method_name: String, args: Vec<String>) -> Self {
18        Self { method_name, args }
19    }
20}
21
22/// Expectation for a method call
23#[derive(Debug, Clone)]
24pub struct Expectation {
25    pub method_name: String,
26    pub expected_args: Option<Vec<String>>,
27    pub return_value: Option<String>,
28    pub call_count: Option<usize>,
29}
30
31impl Expectation {
32    pub fn new(method_name: String) -> Self {
33        Self {
34            method_name,
35            expected_args: None,
36            return_value: None,
37            call_count: None,
38        }
39    }
40
41    pub fn with_args(mut self, args: Vec<String>) -> Self {
42        self.expected_args = Some(args);
43        self
44    }
45
46    pub fn returns(mut self, value: String) -> Self {
47        self.return_value = Some(value);
48        self
49    }
50
51    pub fn times(mut self, count: usize) -> Self {
52        self.call_count = Some(count);
53        self
54    }
55}
56
57/// Mock object for traits
58pub struct MockObject {
59    calls: Arc<Mutex<Vec<MethodCall>>>,
60    expectations: Arc<Mutex<Vec<Expectation>>>,
61    #[allow(clippy::type_complexity)]
62    return_values: Arc<Mutex<HashMap<String, Vec<Box<dyn Any + Send>>>>>,
63}
64
65impl MockObject {
66    pub fn new() -> Self {
67        Self {
68            calls: Arc::new(Mutex::new(Vec::new())),
69            expectations: Arc::new(Mutex::new(Vec::new())),
70            return_values: Arc::new(Mutex::new(HashMap::new())),
71        }
72    }
73
74    /// Record a method call
75    pub fn record_call(&self, method_name: &str, args: Vec<String>) {
76        self.calls
77            .lock()
78            .unwrap()
79            .push(MethodCall::new(method_name.to_string(), args));
80    }
81
82    /// Add an expectation
83    pub fn expect(&self, expectation: Expectation) {
84        self.expectations.lock().unwrap().push(expectation);
85    }
86
87    /// Set return value for a method
88    pub fn set_return<T: 'static + Send>(&self, method_name: &str, value: T) {
89        self.return_values
90            .lock()
91            .unwrap()
92            .entry(method_name.to_string())
93            .or_default()
94            .push(Box::new(value));
95    }
96
97    /// Get return value for a method
98    pub fn get_return<T: 'static>(&self, method_name: &str) -> Option<T> {
99        let mut values = self.return_values.lock().unwrap();
100        let method_values = values.get_mut(method_name)?;
101        if method_values.is_empty() {
102            return None;
103        }
104        let boxed = method_values.remove(0);
105        boxed.downcast::<T>().ok().map(|b| *b)
106    }
107
108    /// Verify all expectations
109    pub fn verify(&self) {
110        let calls = self.calls.lock().unwrap();
111        let expectations = self.expectations.lock().unwrap();
112
113        for expectation in expectations.iter() {
114            let matching_calls: Vec<_> = calls
115                .iter()
116                .filter(|call| call.method_name == expectation.method_name)
117                .collect();
118
119            if let Some(expected_count) = expectation.call_count {
120                if matching_calls.len() != expected_count {
121                    panic!(
122                        "Expected {} calls to '{}', but got {}",
123                        expected_count,
124                        expectation.method_name,
125                        matching_calls.len()
126                    );
127                }
128            }
129
130            if let Some(expected_args) = &expectation.expected_args {
131                let found = matching_calls
132                    .iter()
133                    .any(|call| &call.args == expected_args);
134                if !found {
135                    panic!(
136                        "Expected call to '{}' with args {:?}, but not found",
137                        expectation.method_name, expected_args
138                    );
139                }
140            }
141        }
142    }
143
144    /// Get call count for a method
145    pub fn call_count(&self, method_name: &str) -> usize {
146        self.calls
147            .lock()
148            .unwrap()
149            .iter()
150            .filter(|call| call.method_name == method_name)
151            .count()
152    }
153
154    /// Was method called?
155    pub fn was_called(&self, method_name: &str) -> bool {
156        self.call_count(method_name) > 0
157    }
158
159    /// Get all calls for a method
160    pub fn get_calls(&self, method_name: &str) -> Vec<MethodCall> {
161        self.calls
162            .lock()
163            .unwrap()
164            .iter()
165            .filter(|call| call.method_name == method_name)
166            .cloned()
167            .collect()
168    }
169
170    /// Reset mock state
171    pub fn reset(&self) {
172        self.calls.lock().unwrap().clear();
173        self.expectations.lock().unwrap().clear();
174        self.return_values.lock().unwrap().clear();
175    }
176}
177
178impl Default for MockObject {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184impl Clone for MockObject {
185    fn clone(&self) -> Self {
186        Self {
187            calls: Arc::clone(&self.calls),
188            expectations: Arc::clone(&self.expectations),
189            return_values: Arc::clone(&self.return_values),
190        }
191    }
192}
193
194/// Trait mock helper macro (would be generated by compiler)
195///
196/// Example usage in generated code:
197/// ```
198/// // For trait Database { fn query(&self, sql: &str) -> Vec<Row>; }
199///
200/// struct MockDatabase {
201///     mock: MockObject,
202/// }
203///
204/// impl MockDatabase {
205///     fn new() -> Self {
206///         Self {
207///             mock: MockObject::new(),
208///         }
209///     }
210///
211///     fn expect_query(&self) -> Expectation {
212///         Expectation::new("query".to_string())
213///     }
214/// }
215///
216/// impl Database for MockDatabase {
217///     fn query(&self, sql: &str) -> Vec<Row> {
218///         self.mock.record_call("query", vec![sql.to_string()]);
219///         self.mock.get_return("query").unwrap_or_default()
220///     }
221/// }
222/// ```
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_mock_object_creation() {
229        let mock = MockObject::new();
230        assert_eq!(mock.call_count("test_method"), 0);
231    }
232
233    #[test]
234    fn test_record_call() {
235        let mock = MockObject::new();
236        mock.record_call("query", vec!["SELECT *".to_string()]);
237        assert_eq!(mock.call_count("query"), 1);
238        assert!(mock.was_called("query"));
239    }
240
241    #[test]
242    fn test_multiple_calls() {
243        let mock = MockObject::new();
244        mock.record_call("query", vec!["SELECT *".to_string()]);
245        mock.record_call("query", vec!["INSERT".to_string()]);
246        mock.record_call("insert", vec!["data".to_string()]);
247
248        assert_eq!(mock.call_count("query"), 2);
249        assert_eq!(mock.call_count("insert"), 1);
250    }
251
252    #[test]
253    fn test_get_calls() {
254        let mock = MockObject::new();
255        mock.record_call("query", vec!["SELECT *".to_string()]);
256        mock.record_call("query", vec!["INSERT".to_string()]);
257
258        let calls = mock.get_calls("query");
259        assert_eq!(calls.len(), 2);
260        assert_eq!(calls[0].args[0], "SELECT *");
261        assert_eq!(calls[1].args[0], "INSERT");
262    }
263
264    #[test]
265    fn test_set_and_get_return() {
266        let mock = MockObject::new();
267        mock.set_return("query", 42);
268
269        let result: Option<i32> = mock.get_return("query");
270        assert_eq!(result, Some(42));
271    }
272
273    #[test]
274    fn test_return_values_fifo() {
275        let mock = MockObject::new();
276        mock.set_return("query", 1);
277        mock.set_return("query", 2);
278        mock.set_return("query", 3);
279
280        assert_eq!(mock.get_return::<i32>("query"), Some(1));
281        assert_eq!(mock.get_return::<i32>("query"), Some(2));
282        assert_eq!(mock.get_return::<i32>("query"), Some(3));
283        assert_eq!(mock.get_return::<i32>("query"), None);
284    }
285
286    #[test]
287    fn test_expectation_builder() {
288        let expectation = Expectation::new("query".to_string())
289            .with_args(vec!["SELECT *".to_string()])
290            .returns("result".to_string())
291            .times(2);
292
293        assert_eq!(expectation.method_name, "query");
294        assert_eq!(
295            expectation.expected_args,
296            Some(vec!["SELECT *".to_string()])
297        );
298        assert_eq!(expectation.return_value, Some("result".to_string()));
299        assert_eq!(expectation.call_count, Some(2));
300    }
301
302    #[test]
303    fn test_verify_success() {
304        let mock = MockObject::new();
305        mock.expect(Expectation::new("query".to_string()).times(2));
306
307        mock.record_call("query", vec!["SELECT *".to_string()]);
308        mock.record_call("query", vec!["INSERT".to_string()]);
309
310        mock.verify(); // Should not panic
311    }
312
313    #[test]
314    #[should_panic(expected = "Expected 2 calls")]
315    fn test_verify_call_count_failure() {
316        let mock = MockObject::new();
317        mock.expect(Expectation::new("query".to_string()).times(2));
318
319        mock.record_call("query", vec!["SELECT *".to_string()]);
320
321        mock.verify(); // Should panic
322    }
323
324    #[test]
325    #[should_panic(expected = "Expected call to 'query' with args")]
326    fn test_verify_args_failure() {
327        let mock = MockObject::new();
328        mock.expect(Expectation::new("query".to_string()).with_args(vec!["SELECT *".to_string()]));
329
330        mock.record_call("query", vec!["INSERT".to_string()]);
331
332        mock.verify(); // Should panic
333    }
334
335    #[test]
336    fn test_reset() {
337        let mock = MockObject::new();
338        mock.record_call("query", vec!["SELECT *".to_string()]);
339        mock.set_return("query", 42);
340
341        assert_eq!(mock.call_count("query"), 1);
342
343        mock.reset();
344
345        assert_eq!(mock.call_count("query"), 0);
346        assert_eq!(mock.get_return::<i32>("query"), None);
347    }
348
349    #[test]
350    fn test_mock_clone() {
351        let mock1 = MockObject::new();
352        mock1.record_call("query", vec!["SELECT *".to_string()]);
353
354        let mock2 = mock1.clone();
355
356        // Both share the same state
357        assert_eq!(mock2.call_count("query"), 1);
358
359        mock2.record_call("query", vec!["INSERT".to_string()]);
360        assert_eq!(mock1.call_count("query"), 2); // Changes visible in mock1
361    }
362}