Skip to main content

ranvier_runtime/
testkit.rs

1use crate::Axon;
2use ranvier_core::{Bus, Outcome, transition::ResourceRequirement};
3use serde::{Serialize, de::DeserializeOwned};
4use std::any::Any;
5
6/// Lightweight helper for Axon unit tests.
7///
8/// Provides explicit Bus setup and resource injection so tests can mock
9/// dependencies without hidden framework wiring.
10pub struct AxonTestKit<R> {
11    resources: R,
12    bus: Bus,
13}
14
15impl<R> AxonTestKit<R> {
16    pub fn new(resources: R) -> Self {
17        Self {
18            resources,
19            bus: Bus::new(),
20        }
21    }
22
23    pub fn with_bus(resources: R, bus: Bus) -> Self {
24        Self { resources, bus }
25    }
26
27    pub fn insert<T: Any + Send + Sync + 'static>(&mut self, value: T) -> &mut Self {
28        self.bus.insert(value);
29        self
30    }
31
32    pub fn bus(&self) -> &Bus {
33        &self.bus
34    }
35
36    pub fn bus_mut(&mut self) -> &mut Bus {
37        &mut self.bus
38    }
39
40    pub fn resources(&self) -> &R {
41        &self.resources
42    }
43
44    pub fn resources_mut(&mut self) -> &mut R {
45        &mut self.resources
46    }
47
48    pub fn into_parts(self) -> (R, Bus) {
49        (self.resources, self.bus)
50    }
51}
52
53impl<R> AxonTestKit<R>
54where
55    R: ResourceRequirement,
56{
57    pub async fn run<In, Out, E>(
58        &mut self,
59        axon: &Axon<In, Out, E, R>,
60        input: In,
61    ) -> Outcome<Out, E>
62    where
63        In: Send + Sync + Serialize + DeserializeOwned + 'static,
64        Out: Send + Sync + Serialize + DeserializeOwned + 'static,
65        E: Send + Sync + Serialize + DeserializeOwned + std::fmt::Debug + 'static,
66    {
67        axon.execute(input, &self.resources, &mut self.bus).await
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use ranvier_core::{Outcome, Transition};
75    use serde::{Deserialize, Serialize};
76
77    #[derive(Debug, Clone, Serialize, Deserialize)]
78    pub enum TestInfallible {}
79
80    #[derive(Clone)]
81    struct MockResources {
82        offset: usize,
83    }
84
85    impl ranvier_core::transition::ResourceRequirement for MockResources {}
86
87    #[derive(Clone)]
88    struct SumWithBus;
89
90    #[async_trait::async_trait]
91    impl Transition<usize, usize> for SumWithBus {
92        type Error = TestInfallible;
93        type Resources = MockResources;
94
95        async fn run(
96            &self,
97            state: usize,
98            resources: &Self::Resources,
99            bus: &mut Bus,
100        ) -> Outcome<usize, Self::Error> {
101            let bus_value = bus.read::<usize>().copied().unwrap_or_default();
102            Outcome::next(state + resources.offset + bus_value)
103        }
104    }
105
106    #[tokio::test]
107    async fn testkit_executes_with_mocked_resources_and_bus_values() {
108        let axon = Axon::<usize, usize, TestInfallible, MockResources>::new("Sum").then(SumWithBus);
109        let mut kit = AxonTestKit::new(MockResources { offset: 7 });
110        kit.insert(5usize);
111
112        let result = kit.run(&axon, 10).await;
113        match result {
114            Outcome::Next(value) => assert_eq!(value, 22),
115            _ => panic!("expected Outcome::Next"),
116        }
117    }
118}