[][src]Crate diesel_factories

This is an implementation the test factory pattern made to work with Diesel.

Example usage:

#[macro_use]
extern crate diesel;

use diesel_factories::{Association, Factory};
use diesel::{pg::PgConnection, prelude::*};

// Tell Diesel what our schema is
mod schema {
    table! {
        countries (id) {
            id -> Integer,
            name -> Text,
        }
    }

    table! {
        cities (id) {
            id -> Integer,
            name -> Text,
            country_id -> Integer,
        }
    }
}

// Our city model
#[derive(Clone, Queryable)]
struct City {
    pub id: i32,
    pub name: String,
    pub country_id: i32,
}

#[derive(Clone, Factory)]
#[factory(
    // model type our factory inserts
    model = "City",
    // table the model belongs to
    table = "crate::schema::cities",
    // connection type you use. Defaults to `PgConnection`
    connection = "diesel::pg::PgConnection",
    // type of primary key. Defaults to `i32`
    id = "i32",
)]
struct CityFactory<'a> {
    pub name: String,
    // A `CityFactory` is associated to either an inserted `&'a Country` or a `CountryFactory`
    // instance.
    pub country: Association<'a, Country, CountryFactory>,
}

// We make new factory instances through the `Default` trait
impl<'a> Default for CityFactory<'a> {
    fn default() -> Self {
        Self {
            name: "Copenhagen".to_string(),
            // `default` will return an `Association` with a `CountryFactory`. No inserts happen
            // here.
            //
            // This is the same as `Association::Factory(CountryFactory::default())`.
            country: Association::default(),
        }
    }
}

// The same setup, but for `Country`
#[derive(Clone, Queryable)]
struct Country {
    pub id: i32,
    pub name: String,
}

#[derive(Clone, Factory)]
#[factory(
    model = "Country",
    table = "crate::schema::countries",
    connection = "diesel::pg::PgConnection",
    id = "i32",
)]
struct CountryFactory {
    pub name: String,
}

impl Default for CountryFactory {
    fn default() -> Self {
        Self {
            name: "Denmark".into(),
        }
    }
}

// Usage
fn basic_usage() {
    let con = establish_connection();

    let city = CityFactory::default().insert(&con);
    assert_eq!("Copenhagen", city.name);

    let country = find_country_by_id(city.country_id, &con);
    assert_eq!("Denmark", country.name);

    assert_eq!(1, count_cities(&con));
    assert_eq!(1, count_countries(&con));
}

fn setting_fields() {
    let con = establish_connection();

    let city = CityFactory::default()
        .name("Amsterdam")
        .country(CountryFactory::default().name("Netherlands"))
        .insert(&con);
    assert_eq!("Amsterdam", city.name);

    let country = find_country_by_id(city.country_id, &con);
    assert_eq!("Netherlands", country.name);

    assert_eq!(1, count_cities(&con));
    assert_eq!(1, count_countries(&con));
}

fn multiple_models_with_same_association() {
    let con = establish_connection();

    let netherlands = CountryFactory::default()
        .name("Netherlands")
        .insert(&con);

    let amsterdam = CityFactory::default()
        .name("Amsterdam")
        .country(&netherlands)
        .insert(&con);

    let hague = CityFactory::default()
        .name("The Hague")
        .country(&netherlands)
        .insert(&con);

    assert_eq!(amsterdam.country_id, hague.country_id);

    assert_eq!(2, count_cities(&con));
    assert_eq!(1, count_countries(&con));
}

// Utility functions just for demo'ing
fn count_cities(con: &PgConnection) -> i64 {
    use crate::schema::cities;
    use diesel::dsl::count_star;
    cities::table.select(count_star()).first(con).unwrap()
}

fn count_countries(con: &PgConnection) -> i64 {
    use crate::schema::countries;
    use diesel::dsl::count_star;
    countries::table.select(count_star()).first(con).unwrap()
}

fn find_country_by_id(input: i32, con: &PgConnection) -> Country {
    use crate::schema::countries::dsl::*;
    countries
        .filter(id.eq(&input))
        .first::<Country>(con)
        .unwrap()
}

#[derive(Factory)]

Attributes

Name Description Example Default
model Model type your factory inserts "City" None, required
table Table your model belongs to "crate::schema::cities" None, required
connection The connection type your app uses "MysqlConnection" "diesel::pg::PgConnection"
id The type of your table's primary key "i64" "i32"

Builder methods

Besides implementing Factory for your struct it will also derive builder methods for easily customizing each field. The generated code looks something like this:

struct CountryFactory {
    pub name: String,
}

// This is what gets generated for each field
impl CountryFactory {
    fn name<T: Into<String>>(mut self, new: T) -> Self {
        self.name = new.into();
        self
    }
}

// So you can do this
CountryFactory::default().name("Amsterdam");

Builder methods for associations

The builder methods generated for Association fields are a bit different. If you have a factory like:

#[derive(Clone, Factory)]
#[factory(
    model = "City",
    table = "crate::schema::cities",
)]
struct CityFactory<'a> {
    pub name: String,
    pub country: Association<'a, Country, CountryFactory>,
}

You'll be able to call country either with an owned CountryFactory:

let country_factory = CountryFactory::default();
CityFactory::default().country(country_factory);

Or a borrowed Country:

let country = Country { id: 1, name: "Denmark".into() };
CityFactory::default().country(&country);

This should prevent bugs where you have multiple factory instances sharing some association that you mutate halfway through a test.

Optional associations

If your model has a nullable association you can do this:

#[derive(Clone, Factory)]
#[factory(
    model = "User",
    table = "crate::schema::users",
)]
struct UserFactory<'a> {
    pub name: String,
    pub country: Option<Association<'a, Country, CountryFactory>>,
}

impl<'a> Default for UserFactory<'a> {
    fn default() -> Self {
        Self {
            name: "Bob".into(),
            country: None,
        }
    }
}

// Setting `country` to a `CountryFactory`
let country_factory = CountryFactory::default();
UserFactory::default().country(Some(country_factory));

// Setting `country` to a `Country`
let country = Country { id: 1, name: "Denmark".into() };
UserFactory::default().country(Some(&country));

// Setting `country` to `None`
UserFactory::default().country(Option::<CountryFactory>::None);
UserFactory::default().country(Option::<&Country>::None);

Re-exports

pub use diesel_factories_code_gen::Factory;

Enums

Association

A "belongs to" association that may or may not have been inserted yet.

Traits

Factory

A generic factory trait.

Functions

sequence

Utility function for generating unique ids or strings in factories. Each time sequence gets called, the closure will receive a different number.