Thanks to visit codestin.com
Credit goes to lib.rs

#fixtures #dummy #macro

fixtura

Declarative fake data injection for Rust tests. Built on fake-rs.

17 releases (7 breaking)

Uses new Rust 2024

0.8.5 May 10, 2026
0.7.2 May 9, 2026

#425 in Testing

MIT license

17KB

fixtura

fixtura

crates.io docs.rs license msrv

Declarative fake data injection for Rust tests, built on fake-rs.


Instead of this:

#[test]
fn order_belongs_to_user() {
    let user: User = Faker.fake();
    let mut order: Order = Faker.fake();
    order.user_id = user.id;
    order.status = "pending".to_string();

    assert!(is_billable(&order));
    assert_eq!(order.user_id, user.id);
}

Write this:

#[fixtura::test]
fn order_belongs_to_user(
    user: User,
    #[fixtura(user_id = user.id, status = "pending".to_string())]
    order: Order,
) {
    assert!(is_billable(&order));
    assert_eq!(order.user_id, user.id);
}

The test signature is the setup.


Features

  • Zero-boilerplate injection — declare Dummy types as test arguments; fixtura fakes them all
  • Field overrides — pin the exact values your test cares about; fixtura fakes the rest, including nested paths
  • Cross-arg references — bind a later argument's fields to values from earlier arguments
  • Seeded RNG — every run is reproducible; seeds print on failure and replay on demand
  • Async-ready — pairs with #[tokio::test], async-std, or any async runner via #[fixtura::inject]
  • Framework composable — mix with sqlx::test, axum_test, and others using passthrough args

Installation

[dev-dependencies]
fixtura = "0.8.3"
fake = { version = "5", features = ["derive"] }

Add #[derive(Dummy)] to any type you want injected:

use fake::Dummy;

#[derive(Dummy)]
struct User {
    id: u32,
    name: String,
    active: bool,
}

#[derive(Dummy)]
struct Order {
    id: u32,
    user_id: u32,
    status: String,
}

Quick start

use fake::Dummy;

#[derive(Dummy)]
struct User { id: u32, name: String, active: bool }

#[fixtura::test]
fn active_user_can_login(#[fixtura(active = true)] user: User) {
    assert!(user.active);
}

That's it.


Usage

Inject any Dummy type as a test argument:

#[fixtura::test]
fn user_name_is_never_empty(user: User) {
    assert!(!user.name.is_empty());
}

Pin the fields your test cares about — fixtura fakes the rest:

#[fixtura::test]
fn inactive_users_cannot_checkout(
    #[fixtura(active = false)] user: User,
    order: Order,
) {
    assert!(checkout(&user, &order).is_err());
}

Nested field paths work too:

#[fixtura::test]
fn delivery_uses_city(
    #[fixtura(address.city = "Portland".to_string())] profile: Profile,
) {
    assert_eq!(profile.address.city, "Portland");
}

Reference earlier arguments to keep data coherent:

#[fixtura::test]
fn order_belongs_to_user(
    user: User,
    #[fixtura(user_id = user.id)] order: Order,
) {
    assert_eq!(order.user_id, user.id);
}

Chain references across multiple args:

#[fixtura::test]
fn line_item_links_to_user_and_order(
    user: User,
    #[fixtura(user_id = user.id)] order: Order,
    #[fixtura(user_id = user.id, order_id = order.id)] line: LineItem,
) {
    assert_eq!(line.user_id, user.id);
    assert_eq!(line.order_id, order.id);
}

Async tests

Use #[fixtura::inject] alongside your async runner. It injects args without emitting #[test] — the outer attribute handles that.

Mark each arg fixtura should own with #[fixtura] or #[fixtura(...)]. Field overrides, cross-references, and #[should_panic] all work the same.

#[tokio::test]
#[fixtura::inject]
async fn payment_fails_for_inactive_user(
    #[fixtura(active = false)] user: User,
    #[fixtura(user_id = user.id)] order: Order,
) {
    assert!(process_payment(&user, &order).await.is_err());
}

Note

#[fixtura::inject] must sit below your async runner attribute. Rust applies stacked proc-macro attributes from bottom to top: #[fixtura::inject] transforms the function body first; #[tokio::test] (or your runner) then wraps the result.


Framework passthrough

When combining fixtura with another injecting framework (e.g. sqlx::test), mark only the args fixtura should own. Unmarked args stay in the signature for the other framework to inject.

#[sqlx::test]
#[fixtura::inject]
async fn saves_to_db(
    pool: PgPool,                                // sqlx owns
    #[fixtura] user: User,                       // fixtura owns
    #[fixtura(user_id = user.id)] order: Order,  // fixtura owns, with override
) {
    db::save_order(&pool, &user, &order).await.unwrap();
}

Reproducible failures

Every test uses a seeded RNG. The seed is printed only on failure, so passing tests stay silent:

---- order_belongs_to_user stdout ----
[fixtura] seed = 8317492031748291

Paste it back to replay the exact same values:

#[fixtura::test(seed = 8317492031748291)]
fn order_belongs_to_user(user: User, order: Order) { ... }

Tip

Pin a seed permanently for fully deterministic tests:

#[fixtura::test(seed = 42)]
fn my_test(user: User) { ... }

Works the same with #[fixtura::inject(seed = 42)].


Struct types

Plain injection works for named, tuple, and unit structs — any type that implements Dummy.

Note

#[fixtura(...)] field overrides require named struct fields. Tuple index syntax (0, 1) cannot be used as a field path because it is not a valid identifier. To pin a value, use a named struct instead:

// Tuple struct — plain injection only:
#[derive(Dummy)]
struct Point(f64, f64);

#[fixtura::test]
fn uses_point(p: Point) { let _ = p; }

// To pin a value, use a named struct:
#[derive(Dummy)]
struct Point { x: f64, y: f64 }

#[fixtura::test]
fn pinned_point(#[fixtura(x = 1.0_f64)] p: Point) {
    assert_eq!(p.x, 1.0);
}

IDE support

Note

rust-analyzer provides type-checking and syntax highlighting inside #[fixtura(...)] overrides. Field name completions are not available — there is no stable mechanism for proc-macro crates to provide LSP completions inside attribute arguments. Mistyped field names surface at compile time as ordinary type errors.

Dependencies

~95–440KB
~11K SLoC