fmodel_rust/
specification.rs

1//! ## A test specification DSL for deciders and views that supports the given-when-then format.
2
3use pretty_assertions::assert_eq;
4
5use crate::{
6    decider::{Decider, EventComputation, StateComputation},
7    view::{View, ViewStateComputation},
8};
9
10// ########################################################
11// ############# Decider Specification DSL ################
12// ########################################################
13
14/// A test specification DSL for deciders that supports the `given-when-then` format.
15/// The DSL is used to specify the events that have already occurred (GIVEN), the command that is being executed (WHEN), and the expected events (THEN) that should be generated.
16pub struct DeciderTestSpecification<'a, Command, State, Event, Error>
17where
18    Event: PartialEq + std::fmt::Debug,
19    Error: PartialEq + std::fmt::Debug,
20{
21    events: Vec<Event>,
22    state: Option<State>,
23    command: Option<Command>,
24    decider: Option<Decider<'a, Command, State, Event, Error>>,
25}
26
27impl<Command, State, Event, Error> Default
28    for DeciderTestSpecification<'_, Command, State, Event, Error>
29where
30    Event: PartialEq + std::fmt::Debug,
31    Error: PartialEq + std::fmt::Debug,
32{
33    fn default() -> Self {
34        Self {
35            events: Vec::new(),
36            state: None,
37            command: None,
38            decider: None,
39        }
40    }
41}
42
43impl<'a, Command, State, Event, Error> DeciderTestSpecification<'a, Command, State, Event, Error>
44where
45    Command: std::fmt::Debug,
46    Event: PartialEq + std::fmt::Debug,
47    State: PartialEq + std::fmt::Debug,
48    Error: PartialEq + std::fmt::Debug,
49{
50    #[allow(dead_code)]
51    /// Specify the decider you want to test
52    pub fn for_decider(mut self, decider: Decider<'a, Command, State, Event, Error>) -> Self {
53        self.decider = Some(decider);
54        self
55    }
56
57    #[allow(dead_code)]
58    /// Given preconditions / previous events
59    pub fn given(mut self, events: Vec<Event>) -> Self {
60        self.events = events;
61        self
62    }
63
64    #[allow(dead_code)]
65    /// Given preconditions / previous state
66    pub fn given_state(mut self, state: Option<State>) -> Self {
67        self.state = state;
68        self
69    }
70
71    #[allow(dead_code)]
72    /// When action/command
73    pub fn when(mut self, command: Command) -> Self {
74        self.command = Some(command);
75        self
76    }
77
78    #[allow(dead_code)]
79    #[track_caller]
80    /// Then expect result / new events
81    pub fn then(self, expected_events: Vec<Event>) {
82        let decider = self
83            .decider
84            .expect("Decider must be initialized. Did you forget to call `for_decider`?");
85        let command = self
86            .command
87            .expect("Command must be initialized. Did you forget to call `when`?");
88        let events = self.events;
89
90        let new_events_result = decider.compute_new_events(&events, &command);
91        let new_events = match new_events_result {
92            Ok(events) => events,
93            Err(error) => {
94                panic!("Events were expected but the decider returned an error instead: {error:?}")
95            }
96        };
97        assert_eq!(
98            new_events, expected_events,
99            "Actual and Expected events do not match!\nCommand: {command:?}\n",
100        );
101    }
102
103    #[allow(dead_code)]
104    #[track_caller]
105    /// Then expect result / new events
106    pub fn then_state(self, expected_state: State) {
107        let decider = self
108            .decider
109            .expect("Decider must be initialized. Did you forget to call `for_decider`?");
110        let command = self
111            .command
112            .expect("Command must be initialized. Did you forget to call `when`?");
113        let state = self.state;
114
115        let new_state_result = decider.compute_new_state(state, &command);
116        let new_state = match new_state_result {
117            Ok(state) => state,
118            Err(error) => {
119                panic!("State was expected but the decider returned an error instead: {error:?}")
120            }
121        };
122        assert_eq!(
123            new_state, expected_state,
124            "Actual and Expected states do not match.\nCommand: {command:?}\n"
125        );
126    }
127
128    #[allow(dead_code)]
129    #[track_caller]
130    /// Then expect error result / these are not events
131    pub fn then_error(self, expected_error: Error) {
132        let decider = self
133            .decider
134            .expect("Decider must be initialized. Did you forget to call `for_decider`?");
135        let command = self
136            .command
137            .expect("Command must be initialized. Did you forget to call `when`?");
138        let events = self.events;
139
140        let error_result = decider.compute_new_events(&events, &command);
141        let error = match error_result {
142            Ok(events) => {
143                panic!("An error was expected but the decider returned events instead: {events:?}")
144            }
145            Err(error) => error,
146        };
147        assert_eq!(
148            error, expected_error,
149            "Actual and Expected errors do not match.\nCommand: {command:?}\n"
150        );
151    }
152}
153
154// ########################################################
155// ############### View Specification DSL #################
156// ########################################################
157
158/// A test specification DSL for views that supports the `given-then`` format.
159/// The DSL is used to specify the events that have already occurred (GIVEN), and the expected view state (THEN) that should be generated based on these events.
160pub struct ViewTestSpecification<'a, State, Event>
161where
162    State: PartialEq + std::fmt::Debug,
163{
164    events: Vec<Event>,
165    view: Option<View<'a, State, Event>>,
166}
167
168impl<State, Event> Default for ViewTestSpecification<'_, State, Event>
169where
170    State: PartialEq + std::fmt::Debug,
171{
172    fn default() -> Self {
173        Self {
174            events: Vec::new(),
175            view: None,
176        }
177    }
178}
179
180impl<'a, State, Event> ViewTestSpecification<'a, State, Event>
181where
182    State: PartialEq + std::fmt::Debug,
183    Event: std::fmt::Debug,
184{
185    #[allow(dead_code)]
186    /// Specify the view you want to test
187    pub fn for_view(mut self, view: View<'a, State, Event>) -> Self {
188        self.view = Some(view);
189        self
190    }
191
192    #[allow(dead_code)]
193    /// Given preconditions / events
194    pub fn given(mut self, events: Vec<Event>) -> Self {
195        self.events = events;
196        self
197    }
198
199    #[allow(dead_code)]
200    #[track_caller]
201    /// Then expect evolving new state of the view
202    pub fn then(self, expected_state: State) {
203        let view = self
204            .view
205            .expect("View must be initialized. Did you forget to call `for_view`?");
206
207        let events = self.events;
208
209        let initial_state = (view.initial_state)();
210        let event_refs: Vec<&Event> = events.iter().collect();
211        let new_state_result = view.compute_new_state(Some(initial_state), &event_refs);
212
213        assert_eq!(
214            new_state_result, expected_state,
215            "Actual and Expected states do not match.\nEvents: {events:?}\n"
216        );
217    }
218}