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}