1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//! Django-shape test factories — `factory_boy` parity.
//!
//! `factory_boy` is the de-facto Django fixture builder: declare a
//! factory, get `Factory.build()` (no DB) / `Factory.create()` (DB)
//! and `Factory.create_batch(n)` for free, with `Sequence(...)` for
//! per-call unique values. The Python design exists because Python
//! has no struct-update syntax — building a `User(name=..., email=...,
//! created_at=...)` ten times is tedious.
//!
//! Rust *does* have struct update syntax + `Default::default()`, so
//! you can already write:
//!
//! ```ignore
//! let users: Vec<User> = (0..10)
//! .map(|i| User { name: format!("user-{i}"), ..Default::default() })
//! .collect();
//! ```
//!
//! This module ships the two factory_boy primitives that are still
//! useful on top of that: a [`Sequence`] for thread-safe per-call
//! unique values, and a [`Factory`] trait that adds [`Factory::build_batch`]
//! / [`Factory::build_pool`] for the create-many DB pattern.
//!
//! ## Example
//!
//! ```ignore
//! use rustango::test_factory::{Factory, Sequence};
//!
//! struct UserFactory {
//! usernames: Sequence<String>,
//! }
//!
//! impl Default for UserFactory {
//! fn default() -> Self {
//! Self {
//! usernames: Sequence::new(|n| format!("user-{n}")),
//! }
//! }
//! }
//!
//! impl Factory for UserFactory {
//! type Item = User;
//! fn build(&self) -> User {
//! User { username: self.usernames.next(), ..Default::default() }
//! }
//! }
//!
//! let f = UserFactory::default();
//! let three = f.build_batch(3);
//! assert_eq!(three[0].username, "user-0");
//! assert_eq!(three[2].username, "user-2");
//! ```
//!
//! Issue #432.
use std::sync::atomic::{AtomicU64, Ordering};
/// Atomically-incrementing counter that produces a fresh value on
/// each [`Sequence::next`] call. Mirrors `factory.Sequence(lambda n: ...)`.
///
/// The closure is called with the current counter value (starting
/// at `0`) and returns the per-call output. Counter is `AtomicU64`,
/// so the sequence is safe to share across threads / tasks — multiple
/// tests calling `factory.usernames.next()` in parallel each get a
/// unique value.
///
/// `Sequence` does not implement `Clone` — every clone would share
/// the same atomic counter, which is usually NOT what callers want
/// (each factory instance should have its own sequence). Build a
/// new `Sequence::new(...)` per factory.
pub struct Sequence<T> {
counter: AtomicU64,
factory: Box<dyn Fn(u64) -> T + Send + Sync>,
}
impl<T> Sequence<T> {
/// Build a sequence whose `next()` returns `factory(0)`,
/// `factory(1)`, `factory(2)`, ...
pub fn new<F>(factory: F) -> Self
where
F: Fn(u64) -> T + Send + Sync + 'static,
{
Self {
counter: AtomicU64::new(0),
factory: Box::new(factory),
}
}
/// Increment the internal counter and return the next value.
pub fn next(&self) -> T {
let n = self.counter.fetch_add(1, Ordering::Relaxed);
(self.factory)(n)
}
/// Current counter value (the index the next `next()` call will
/// see). Useful in assertions.
#[must_use]
pub fn current(&self) -> u64 {
self.counter.load(Ordering::Relaxed)
}
/// Reset the counter to `0`. Useful between test runs that need
/// deterministic IDs.
pub fn reset(&self) {
self.counter.store(0, Ordering::Relaxed);
}
}
/// Trait for factory_boy-shape model builders.
///
/// Implementors override [`Factory::build`] to construct one in-memory
/// instance — the rest (`build_batch`, `build_pool`, `build_batch_pool`)
/// are derived.
///
/// `Item` is the type the factory produces — typically a model struct
/// or a DTO.
pub trait Factory {
type Item;
/// Build one in-memory instance. Implementations usually pull
/// per-call unique values out of internal [`Sequence`] fields.
fn build(&self) -> Self::Item;
/// Build `n` in-memory instances by calling `build()` in a loop.
fn build_batch(&self, n: usize) -> Vec<Self::Item> {
(0..n).map(|_| self.build()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sequence_emits_zero_one_two() {
let s = Sequence::new(|n| format!("user-{n}"));
assert_eq!(s.next(), "user-0");
assert_eq!(s.next(), "user-1");
assert_eq!(s.next(), "user-2");
assert_eq!(s.current(), 3);
}
#[test]
fn sequence_reset_restarts_counter() {
let s = Sequence::new(|n| n * 2);
let _ = s.next();
let _ = s.next();
s.reset();
assert_eq!(s.next(), 0);
assert_eq!(s.next(), 2);
}
#[test]
fn sequence_is_thread_safe() {
use std::sync::Arc;
let s = Arc::new(Sequence::new(|n| n));
let handles: Vec<_> = (0..16)
.map(|_| {
let s = Arc::clone(&s);
std::thread::spawn(move || s.next())
})
.collect();
let mut values: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
values.sort_unstable();
assert_eq!(values, (0..16).collect::<Vec<_>>());
}
#[derive(Debug, Clone, PartialEq, Default)]
struct User {
username: String,
age: u32,
}
struct UserFactory {
usernames: Sequence<String>,
}
impl Default for UserFactory {
fn default() -> Self {
Self {
usernames: Sequence::new(|n| format!("user-{n}")),
}
}
}
impl Factory for UserFactory {
type Item = User;
fn build(&self) -> User {
User {
username: self.usernames.next(),
age: 30,
}
}
}
#[test]
fn factory_build_uses_sequence() {
let f = UserFactory::default();
assert_eq!(f.build().username, "user-0");
assert_eq!(f.build().username, "user-1");
}
#[test]
fn factory_build_batch_returns_n_items() {
let f = UserFactory::default();
let batch = f.build_batch(5);
assert_eq!(batch.len(), 5);
assert_eq!(batch[0].username, "user-0");
assert_eq!(batch[4].username, "user-4");
assert!(batch.iter().all(|u| u.age == 30));
}
}