Skip to main content

rainier_database/
factory.rs

1//! Test fixtures — [`Factory`] and [`HasFactory`].
2//!
3//! ```ignore
4//! #[derive(Entity, Default, Clone, Factory)]
5//! struct User { id: u64, email: String, verified_at: Option<DateTime<Utc>> }
6//!
7//! let users = User::factory()
8//!     .count(3)
9//!     .sequence(|user, i| user.email = format!("user{i}@example.com"))
10//!     .state(|user| user.verified_at = Some(Utc::now()))
11//!     .create(&*users)
12//!     .await?;
13//! ```
14//!
15//! [`TestApp`](https://docs.rs/rainier-framework) solved booting an
16//! application. This is the other half: getting rows into it without every
17//! test constructing them field by field, which is why a suite's setup ends up
18//! longer than its assertions.
19//!
20//! # What a factory is for, and what it is not
21//!
22//! It builds a row that is **valid and uninteresting**. Every field a test
23//! does not care about gets a value that will not trip a constraint, and the
24//! one or two fields the test *is* about are set with [`state`](Factory::state).
25//!
26//! That is the whole point: a test that spells out fifteen fields to assert on
27//! one of them has buried its subject. A reader cannot tell which value
28//! matters, and neither can the next person to change the schema.
29//!
30//! # Unique columns need a sequence
31//!
32//! A derived factory builds from [`Default`], and three defaults are three
33//! identical rows — which a `UNIQUE` index refuses on the second. Anything
34//! unique needs [`sequence`](Factory::sequence), which is handed the index:
35//!
36//! ```ignore
37//! .sequence(|user, i| user.email = format!("user{i}@example.com"))
38//! ```
39//!
40//! Deliberately not automatic. A factory that invented unique values would
41//! have to guess which columns are unique and what shape they take, and a
42//! guess that is wrong produces a row that fails to insert for a reason nobody
43//! can see from the test.
44
45use std::sync::Arc;
46
47use rainier_support::Result;
48
49use crate::model::Model;
50use crate::repository::Repository;
51
52/// Sets one aspect of a model being built.
53type State<M> = Arc<dyn Fn(&mut M, usize) + Send + Sync>;
54
55/// Builds models for a test.
56pub struct Factory<M> {
57    make: Arc<dyn Fn(usize) -> M + Send + Sync>,
58    states: Vec<State<M>>,
59    count: usize,
60}
61
62impl<M> Factory<M> {
63    /// Build each model with `make`, which is handed the row's index.
64    pub fn new(make: impl Fn(usize) -> M + Send + Sync + 'static) -> Self {
65        Self { make: Arc::new(make), states: Vec::new(), count: 1 }
66    }
67
68    /// How many to build.
69    #[must_use = "this returns a configured factory rather than configuring in place"]
70    pub fn count(mut self, count: usize) -> Self {
71        self.count = count;
72        self
73    }
74
75    /// Adjust every model this factory builds.
76    ///
77    /// The field the test is actually about. Applied in the order added, after
78    /// the base model is built.
79    #[must_use = "this returns a configured factory rather than configuring in place"]
80    pub fn state(self, state: impl Fn(&mut M) + Send + Sync + 'static) -> Self {
81        self.sequence(move |model, _| state(model))
82    }
83
84    /// Adjust each model, knowing which one it is.
85    ///
86    /// For anything that has to differ between rows — a unique email, an
87    /// ordering, a timestamp that has to be distinct.
88    #[must_use = "this returns a configured factory rather than configuring in place"]
89    pub fn sequence(mut self, state: impl Fn(&mut M, usize) + Send + Sync + 'static) -> Self {
90        self.states.push(Arc::new(state));
91        self
92    }
93
94    /// Build them, without touching a database.
95    ///
96    /// For a test about a pure function, a serialiser or a policy — none of
97    /// which need a row to exist anywhere.
98    pub fn make(&self) -> Vec<M> {
99        (0..self.count)
100            .map(|index| {
101                let mut model = (self.make)(index);
102                for state in &self.states {
103                    state(&mut model, index);
104                }
105                model
106            })
107            .collect()
108    }
109
110    /// Build exactly one.
111    ///
112    /// # Panics
113    ///
114    /// Never — [`count`](Self::count) is ignored and one is built.
115    pub fn make_one(&self) -> M {
116        let mut model = (self.make)(0);
117        for state in &self.states {
118            state(&mut model, 0);
119        }
120        model
121    }
122}
123
124impl<M: Model> Factory<M> {
125    /// Build them and insert them.
126    ///
127    /// Returns what the repository returned — so a database-assigned key is on
128    /// the model a test goes on to use, rather than the zero it was built
129    /// with.
130    pub async fn create(&self, repository: &dyn Repository<M>) -> Result<Vec<M>> {
131        let mut created = Vec::with_capacity(self.count);
132
133        // Sequentially, and deliberately. Concurrent inserts against one
134        // connection interleave in whatever order the pool feels like, which
135        // makes a test that asserts on ordering fail once a fortnight.
136        for model in self.make() {
137            created.push(repository.create(model).await?);
138        }
139
140        Ok(created)
141    }
142
143    /// Build one and insert it.
144    pub async fn create_one(&self, repository: &dyn Repository<M>) -> Result<M> {
145        repository.create(self.make_one()).await
146    }
147}
148
149impl<M> Clone for Factory<M> {
150    fn clone(&self) -> Self {
151        Self { make: Arc::clone(&self.make), states: self.states.clone(), count: self.count }
152    }
153}
154
155impl<M> std::fmt::Debug for Factory<M> {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        f.debug_struct("Factory")
158            .field("count", &self.count)
159            .field("states", &self.states.len())
160            .finish()
161    }
162}
163
164/// A model with a factory.
165///
166/// Implemented by `#[derive(Factory)]`, which needs [`Default`] — or by hand,
167/// when the default row is not a sensible starting point:
168///
169/// ```ignore
170/// impl HasFactory for User {
171///     fn factory() -> Factory<Self> {
172///         Factory::new(|i| User {
173///             id: 0,
174///             email: format!("user{i}@example.com"),
175///             verified_at: None,
176///         })
177///     }
178/// }
179/// ```
180///
181/// Writing it by hand is often the better answer for a model with unique
182/// columns: it puts the sequence in one place instead of in every test.
183pub trait HasFactory: Sized {
184    /// A factory building one of these.
185    fn factory() -> Factory<Self>;
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[derive(Debug, Clone, Default, PartialEq)]
193    struct User {
194        id: u64,
195        email: String,
196        admin: bool,
197    }
198
199    impl HasFactory for User {
200        fn factory() -> Factory<Self> {
201            Factory::new(|_| User::default())
202        }
203    }
204
205    #[test]
206    fn one_by_default() {
207        let users = User::factory().make();
208
209        assert_eq!(users.len(), 1);
210        assert_eq!(users[0], User::default());
211    }
212
213    #[test]
214    fn count_builds_that_many() {
215        assert_eq!(User::factory().count(3).make().len(), 3);
216        assert_eq!(User::factory().count(0).make().len(), 0);
217    }
218
219    #[test]
220    fn state_applies_to_every_one() {
221        let users = User::factory().count(3).state(|user| user.admin = true).make();
222
223        assert!(users.iter().all(|user| user.admin));
224    }
225
226    #[test]
227    fn a_sequence_knows_which_row_it_is() {
228        // The answer to a UNIQUE index, and the reason `state` is not enough.
229        let users = User::factory()
230            .count(3)
231            .sequence(|user, i| user.email = format!("user{i}@example.com"))
232            .make();
233
234        assert_eq!(users[0].email, "user0@example.com");
235        assert_eq!(users[2].email, "user2@example.com");
236
237        let emails: std::collections::HashSet<_> = users.iter().map(|user| &user.email).collect();
238        assert_eq!(emails.len(), 3, "a unique column needs distinct values");
239    }
240
241    #[test]
242    fn states_apply_in_the_order_they_were_added() {
243        // So a later one can override an earlier one, which is how a shared
244        // base factory gets specialised by one test.
245        let users = User::factory()
246            .state(|user| user.email = "first".into())
247            .state(|user| user.email = "second".into())
248            .make();
249
250        assert_eq!(users[0].email, "second");
251    }
252
253    #[test]
254    fn make_one_ignores_the_count() {
255        let user = User::factory().count(5).state(|user| user.admin = true).make_one();
256
257        assert!(user.admin);
258    }
259
260    #[test]
261    fn a_factory_can_be_cloned_and_specialised() {
262        // The pattern a test suite converges on: one base factory, several
263        // narrower ones.
264        let base = User::factory().count(2);
265
266        let admins = base.clone().state(|user| user.admin = true).make();
267        let ordinary = base.make();
268
269        assert!(admins.iter().all(|user| user.admin));
270        assert!(!ordinary.iter().any(|user| user.admin));
271    }
272
273    #[test]
274    fn a_hand_written_factory_can_do_what_a_derived_one_cannot() {
275        struct Post {
276            slug: String,
277        }
278
279        impl HasFactory for Post {
280            fn factory() -> Factory<Self> {
281                Factory::new(|i| Post { slug: format!("post-{i}") })
282            }
283        }
284
285        let posts = Post::factory().count(2).make();
286
287        assert_eq!(posts[0].slug, "post-0");
288        assert_eq!(posts[1].slug, "post-1");
289    }
290}