Expand description
Factory Derive Macros
Generates factory boilerplate for test entities with automatic FK resolution.
§Example Usage
ⓘ
use factory_m8::FactoryCreate;
use factory_derive::Factory;
#[derive(Debug, Default, Factory)]
#[factory(entity = Patient)]
pub struct PatientFactory {
#[pk]
pub id: PatientId,
// Option<T> FK - auto-creates if None/sentinel, result is Some(id)
// Factory field type should match entity field type
#[fk(Practice, "id", PracticeFactory)]
pub practice_id: Option<PracticeId>,
// Non-Option FK - auto-creates if is_sentinel() returns true
// Default impl typically sets to sentinel value (e.g., Id(0))
#[fk(Tenant, "id", TenantFactory)]
pub tenant_id: TenantId,
// Option<T> FK with no_default - won't auto-create, None stays None
// Use for truly optional FKs where entity field is also Option
#[fk(Provider, "id", ProviderFactory, no_default)]
pub provider_id: Option<ProviderId>,
// Non-Option field - used directly (provide in Default impl)
pub name: String,
// Option field - cloned as-is (truly optional)
pub nickname: Option<String>,
}
// User implements just the INSERT
impl FactoryCreate for PatientFactory {
type Entity = Patient;
async fn create(self, pool: &PgPool) -> Result<Patient, Box<dyn Error + Send + Sync>> {
let entity = self.build_with_fks(pool).await?;
sqlx::query_as!(Patient, "INSERT INTO patient ...")
.fetch_one(pool).await
}
}§Attributes
#[factory(entity = EntityType)]- Specifies the entity type this factory creates#[pk]- Primary key field, uses Default::default()#[fk(Entity, "field", Factory)]- FK field, optionality based on field type:Option<T>: auto-creates if None/unset, returnsSome(id)T(non-Option): auto-creates ifis_unset(), returnsid
#[fk(Entity, "field", Factory, no_default)]- Don’t auto-create, None stays None
§FK Field Types
FK field type determines behavior in build_with_fks():
-
Option<IdType>: Auto-creates if None or sentinel, returnsSome(created_id). Useno_defaultflag to disable auto-creation (None stays None). -
IdType(non-Option): Auto-creates ifis_sentinel()returns true. Default impl should set to sentinel value (e.g.,Id(0)).
Important: Factory field type should match entity field type.
§Generated Methods
new()- Creates factory with default valueswith_<entity>(&Entity)- Sets FK from entity referencewith_<field>_id(Id)- Sets FK ID directlywith_<field>(value)- Sets field value (for Option and non-Option fields)build()- Creates entity in-memory (clones Option FK fields as-is)build_with_fks(pool)- Creates entity, auto-creating FK dependencies if needed