u/BrumaRaL

I deleted 5 helper functions from my test suite by making test data declarative

There's a pattern I've seen in almost every test suite: a create_test_user() function. It builds a struct with sensible defaults so tests don't have to construct one from scratch.

It starts small. Then it grows. After a year it has 7 parameters and you have to read its definition to understand any test that calls it — because you can't tell from the call site which arguments matter and which are just noise.

// What is arg 3? arg 5? Does this test care about created_at?
// You have to check.
let user = create_test_user(1, "alice", "admin", true, created_at, ...);

The fix I landed on: make the setup declarative and put it at the call site. Only declare what the test actually depends on. Let everything else be randomly generated.

fn role_based_access(
    admin: User  { role: "admin", ...everything else random },
    viewer: User { role: "viewer", ...everything else random },
) {
    // test only cares about role — and now that's obvious
}

This is what fixtura does for Rust. It's a small library built on top of fake-rs that wires up random struct generation and lets you override only the fields that matter for a given test.

The gains after migrating CILens, a CI analytics tool I built:

  • Deleted 5 helper functions entirely
  • Test suite shrank by 25%
  • ~120 struct fields that were hardcoded for no reason are now faked freely
  • Tests read like specifications: if a field isn't in the parameter list, the test doesn't depend on it

The broader principle works in any language: test setup should declare intent, not construction. The more a test hides in a helper, the harder it is to understand what the test is actually verifying.

https://github.com/dsalaza4/fixturaa (Rust-specific, but the idea isn't)

crates.io
u/BrumaRaL — 12 days ago
▲ 18 r/rust

fixtura – declarative test data injection for Rust. Pin only what your test depends on, fake the rest.

fixtura lets you declare exactly what each test parameter depends on and fakes everything else automatically. Built on fake-rs.

How it works

Add #[cfg_attr(test, derive(fake::Dummy))] to your struct, then declare test parameters with only the fields that matter:

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

Only active is pinned because only active drives the logic. Everything else — IDs, names, timestamps — is faked automatically.

Cross-argument references work too:

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

Real-world result

I migrated CILens, a CI analytics tool I built, to fixtura:

  • 5 create_test_* helpers deleted
  • Test suite shrank by 25%
  • ~120 struct fields that were hardcoded for no reason now freely faked

The before/after is in the PRs if you want to see what an actual migration looks like:

Limitations I'm honest about

  • Tuple structs: field overrides aren't supported by position, only by name. Workaround: use a named struct or wrap the tuple struct.
  • Async tests: works fine, but the attribute ordering matters (#[fixtura::test] must come before async runtime attributes like #[tokio::test]).
  • Requires fake::Dummy on your types — either derive it or impl manually.

Happy to answer questions. Feedback on the API or missing features welcome.

u/BrumaRaL — 12 days ago