rust_elm/testing/
test_runtime.rs1use crate::cmd::Cmd;
2use crate::effect::Effect;
3
4pub 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#[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}