mockable/
mock.rs

1use std::sync::{
2    atomic::{AtomicUsize, Ordering},
3    Arc,
4};
5
6type MockFn<RETURN, ARGS> = dyn Fn(ARGS) -> RETURN + Send + Sync;
7
8/// Struct that represents a function mock.
9///
10/// **This is supported on `feature=mock` only.**
11///
12/// [Example](https://github.com/leroyguillaume/mockable/tree/main/examples/mock.rs).
13pub struct Mock<RETURN, ARGS = ()> {
14    idx: Arc<AtomicUsize>,
15    kind: MockKind<RETURN, ARGS>,
16}
17
18impl<RETURN, ARGS> Mock<RETURN, ARGS> {
19    /// Creates a new `Mock` that always returns always the same result.
20    pub fn always_with_args<F: Fn(usize, ARGS) -> RETURN + Send + Sync + 'static>(f: F) -> Self {
21        Self {
22            idx: Arc::new(AtomicUsize::new(0)),
23            kind: MockKind::Always(Arc::new(Box::new(f))),
24        }
25    }
26
27    /// Creates a new `Mock` that should never be called.
28    pub fn never() -> Self {
29        Self::with(vec![])
30    }
31
32    /// Creates a new `Mock` that should be called only once.
33    pub fn once_with_args<F: Fn(ARGS) -> RETURN + Send + Sync + 'static>(f: F) -> Self {
34        Self::with(vec![Box::new(f)])
35    }
36
37    /// Creates a new `Mock` that should be called several times.
38    pub fn with(f: Vec<Box<dyn Fn(ARGS) -> RETURN + Send + Sync>>) -> Self {
39        Self {
40            idx: Arc::new(AtomicUsize::new(0)),
41            kind: MockKind::CallSpecific(Arc::new(f)),
42        }
43    }
44
45    /// Returns the result of the mock.
46    ///
47    /// # Panics
48    /// Panics if the mock has been called more times than expected.
49    pub fn call_with_args(&self, args: ARGS) -> RETURN {
50        let idx = self.idx.fetch_add(1, Ordering::Relaxed);
51        match &self.kind {
52            MockKind::Always(f) => f(idx, args),
53            MockKind::CallSpecific(fns) => {
54                if idx >= fns.len() {
55                    panic!("Mock called when it should not have been");
56                }
57                fns[idx](args)
58            }
59        }
60    }
61
62    /// Returns the number of times the mock has been called.
63    pub fn count(&self) -> usize {
64        self.idx.load(Ordering::Relaxed)
65    }
66
67    /// Returns the number of times the mock is expected to be called.
68    ///
69    /// If the mock is expected to return always the same value, `usize::MAX` is returned.
70    pub fn times(&self) -> usize {
71        match &self.kind {
72            MockKind::Always(_) => usize::MAX,
73            MockKind::CallSpecific(fns) => fns.len(),
74        }
75    }
76}
77
78impl<RETURN> Mock<RETURN, ()> {
79    /// Creates a new `Mock` that always returns always the same result.
80    pub fn always<F: Fn(usize) -> RETURN + Send + Sync + 'static>(f: F) -> Self {
81        Self::always_with_args(move |idx, _| f(idx))
82    }
83
84    /// Creates a new `Mock` that should be called only once.
85    pub fn once<F: Fn() -> RETURN + Send + Sync + 'static>(f: F) -> Self {
86        Self::once_with_args(move |_| f())
87    }
88
89    /// Returns the result of the mock.
90    ///
91    /// # Panics
92    /// Panics if the mock has been called more times than expected.
93    pub fn call(&self) -> RETURN {
94        self.call_with_args(())
95    }
96}
97
98impl<RETURN, ARGS> Clone for Mock<RETURN, ARGS> {
99    fn clone(&self) -> Self {
100        Self {
101            idx: self.idx.clone(),
102            kind: self.kind.clone(),
103        }
104    }
105}
106
107impl<RETURN, ARGS> Default for Mock<RETURN, ARGS> {
108    fn default() -> Self {
109        Self::never()
110    }
111}
112
113// MockKind
114
115enum MockKind<RETURN, ARGS> {
116    Always(Arc<Box<dyn Fn(usize, ARGS) -> RETURN + Send + Sync>>),
117    CallSpecific(Arc<Vec<Box<MockFn<RETURN, ARGS>>>>),
118}
119
120impl<RETURN, ARGS> Clone for MockKind<RETURN, ARGS> {
121    fn clone(&self) -> Self {
122        match self {
123            MockKind::Always(f) => MockKind::Always(f.clone()),
124            MockKind::CallSpecific(fns) => MockKind::CallSpecific(fns.clone()),
125        }
126    }
127}