Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Simplify Complex Test Setup with Factories

Testing a feature that spans users, orders, order lines, and products requires creating several related records. Without factories, each test starts with a wall of manual inserts. Factories let you express the same setup in a few chained calls.

For the full factory API, see Factories. For writing tests with #[fabrique::test], see Testing with Fabrique.

Seed a Full Order in One Chain

An order needs a user, products, and the join records linking them. With factories, the entire graph is built in one chain — missing parents are auto-created:

extern crate fabrique;
extern crate sqlx;
extern crate tokio;
extern crate uuid;
use fabrique::prelude::*;
use uuid::Uuid;

#[derive(Clone, Factory, Model)]
pub struct User {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub orders: HasMany<Order>,
}
#[derive(Clone, Factory, Model)]
pub struct Product {
    pub id: Uuid,
    pub name: String,
    pub price_cents: i32,
    pub in_stock: bool,
}
#[derive(Clone, Factory, Model)]
pub struct Order {
    pub id: Uuid,
    #[fabrique(belongs_to = "User")]
    pub user_id: Uuid,
    pub status: String,
    #[fabrique(through = "OrderLine")]
    pub products: HasMany<Product>,
}
#[derive(Clone, Factory, Model)]
#[fabrique(table = "order_lines")]
pub struct OrderLine {
    #[fabrique(primary_key, belongs_to = "Order")]
    pub order_id: Uuid,
    #[fabrique(primary_key, belongs_to = "Product")]
    pub product_id: Uuid,
    pub quantity: i32,
    pub unit_price_cents: i32,
}

#[fabrique::doctest]
async fn main(pool: Pool<Backend>) -> Result<(), fabrique::Error> {
let order = Order::factory()
    .has_products(Product::factory(), 3)
    .create(&pool)
    .await?;
Ok(())
}

This single chain creates 1 user, 1 order, 3 products, and 3 order lines — 8 records total. The factory resolves the foreign keys automatically.

Share a Parent Across Children

Three orders for the same customer. Pass a factory instance to each for_user — the first .create() persists it, the next ones reuse the same record:

extern crate fabrique;
extern crate sqlx;
extern crate tokio;
extern crate uuid;
use fabrique::prelude::*;
use uuid::Uuid;

#[derive(Clone, Factory, Model)]
pub struct User {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub orders: HasMany<Order>,
}
#[derive(Clone, Factory, Model)]
pub struct Order {
    pub id: Uuid,
    #[fabrique(belongs_to = "User")]
    pub user_id: Uuid,
    pub status: String,
}

#[fabrique::doctest]
async fn main(pool: Pool<Backend>) -> Result<(), fabrique::Error> {
let user = User::factory()
    .name("Wile E. Coyote".to_string())
    .create(&pool)
    .await?;

let pending = Order::factory()
    .for_user(user.clone())
    .status("pending".to_string())
    .create(&pool)
    .await?;

let shipped = Order::factory()
    .for_user(user.clone())
    .status("shipped".to_string())
    .create(&pool)
    .await?;

assert_eq!(pending.user_id, shipped.user_id);
Ok(())
}

for_user accepts both a model instance and a factory. When you pass an already-created instance, no extra INSERT happens.

Override Only What Matters

Testing a status filter? Only set status — let the factory generate everything else:

extern crate fabrique;
extern crate sqlx;
extern crate tokio;
extern crate uuid;
use fabrique::prelude::*;
use uuid::Uuid;

#[derive(Clone, Factory, Model)]
pub struct User {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub orders: HasMany<Order>,
}
#[derive(Clone, Factory, Model)]
pub struct Order {
    pub id: Uuid,
    #[fabrique(belongs_to = "User")]
    pub user_id: Uuid,
    pub status: String,
}

#[fabrique::doctest]
async fn main(pool: Pool<Backend>) -> Result<(), fabrique::Error> {
// Arrange — only the status matters for this test
Order::factory()
    .status("shipped".to_string())
    .create(&pool)
    .await?;

Order::factory()
    .status("pending".to_string())
    .create(&pool)
    .await?;

// Act
let pending: Vec<Order> = Order::query()
    .r#where(Order::STATUS, "=", "pending")
    .get(&pool)
    .await?;

// Assert
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].status, "pending");
Ok(())
}

The test reads at a glance: two orders with different statuses, query for one. No noise from IDs, names, or emails.

Handle Ambiguous Relations

When a model has multiple foreign keys to the same parent (e.g. Message with sender_id and recipient_id), use aliases to disambiguate. Fabrique then generates a dedicated for_<alias>() method for each relationship.

Here, Alice and Bob each send 100 messages to each other — 200 messages total, seeded in a loop:

extern crate fabrique;
extern crate sqlx;
extern crate tokio;
extern crate uuid;
use fabrique::prelude::*;
use uuid::Uuid;

#[derive(Clone, Factory, Model)]
pub struct User {
    pub id: Uuid,
    pub name: String,
    pub email: String,
}
#[derive(Clone, Factory, Model)]
pub struct Message {
    pub id: Uuid,
    pub content: String,
    #[fabrique(belongs_to = "User", alias = "Sender")]
    pub sender_id: Uuid,
    #[fabrique(belongs_to = "User", alias = "Recipient")]
    pub recipient_id: Uuid,
}

#[fabrique::doctest]
async fn main(pool: Pool<Backend>) -> Result<(), fabrique::Error> {
let alice = User::factory()
    .name("Alice".to_string())
    .create(&pool)
    .await?;
let bob = User::factory()
    .name("Bob".to_string())
    .create(&pool)
    .await?;

// Alice sends 100 messages to Bob
for _ in 0..100 {
    Message::factory()
        .for_sender(alice.clone())
        .for_recipient(bob.clone())
        .create(&pool)
        .await?;
}

// Bob sends 100 messages to Alice
for _ in 0..100 {
    Message::factory()
        .for_sender(bob.clone())
        .for_recipient(alice.clone())
        .create(&pool)
        .await?;
}
Ok(())
}

For more details on aliases, see Handle Ambiguous Relations.