Skip to main content

rust_elm/testing/
test_runtime.rs

1use crate::cmd::Cmd;
2use crate::effect::Effect;
3
4/// Synchronous test runtime for asserting effect descriptions without Tokio.
5pub struct TestRuntime<S, M> {
6    pub state: S,
7    pub update: fn(&mut S, M) -> Cmd<M>,
8    pub pending_effects: Vec<Effect<M>>,
9}
10
11impl<S, M> TestRuntime<S, M>
12where
13    M: Clone + Send + 'static,
14{
15    pub fn new(state: S, update: fn(&mut S, M) -> Cmd<M>) -> Self {
16        Self {
17            state,
18            update,
19            pending_effects: Vec::new(),
20        }
21    }
22
23    pub fn send(&mut self, action: M) -> Cmd<M> {
24        let cmd = (self.update)(&mut self.state, action);
25        self.pending_effects.extend(cmd.clone().into_effects());
26        cmd
27    }
28
29    pub fn drain_effects(&mut self) -> Vec<Effect<M>> {
30        std::mem::take(&mut self.pending_effects)
31    }
32
33    pub fn assert_no_pending_effects(&self) {
34        assert!(
35            self.pending_effects.is_empty(),
36            "expected no pending effects, found {}",
37            self.pending_effects.len()
38        );
39    }
40}
41
42/// Assert that the last command contains an effect matching a predicate.
43#[macro_export]
44macro_rules! assert_effect {
45    ($runtime:expr, $variant:pat) => {{
46        let effects = $runtime.drain_effects();
47        assert!(
48            effects.iter().any(|e| matches!(e, $variant)),
49            "expected effect matching {}, got {:?}",
50            stringify!($variant),
51            effects
52        );
53    }};
54}
55
56pub use assert_effect;
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::EffectError;
62
63    #[derive(Default)]
64    struct S {
65        n: i32,
66    }
67
68    fn update(s: &mut S, msg: i32) -> Cmd<i32> {
69        s.n = msg;
70        Cmd::single(Effect::task(1, effect_echo))
71    }
72
73    fn effect_echo() -> std::pin::Pin<
74        Box<dyn std::future::Future<Output = Result<i32, EffectError>> + Send>,
75    > {
76        Box::pin(async { Ok(7) })
77    }
78
79    #[test]
80    fn test_runtime_collects_effects() {
81        let mut rt = TestRuntime::new(S::default(), update);
82        rt.send(5);
83        assert_eq!(rt.pending_effects.len(), 1);
84        rt.drain_effects();
85        rt.assert_no_pending_effects();
86    }
87}