fixtures

Crates.iofixtures
lib.rsfixtures
version2.3.2
created_at2023-09-17 16:26:32.178834+00
updated_at2025-09-21 23:49:22.689604+00
descriptionRun tests against fixtures
homepagehttps://github.com/bcheidemann/fixtures-rs
repositoryhttps://github.com/bcheidemann/fixtures-rs
max_upload_size
id975213
size42,337
Ben Heidemann (bcheidemann)

documentation

README

fixtures

fixtures is a Rust crate which allows developers to run tests against fixture files. It provides a procedural macro to generate tests from the filesystem, using glob patterns.

Example

#[fixtures(["fixtures/*.txt"])]
#[test]
fn test(path: &std::path::Path) {
  // This test will be run once for each file matching the glob pattern
}

To ensure tests re-run when the fixtures change, add the following line to build.rs.

fixtures::build::watch_dir("fixtures");

Comparison to datatest and datatest-stable

fixtures datatest datatest-stable
Supports stable rust 🏅 yes no 🏅 yes
Requires setting harness = false in Cargo.toml 🏅 no no yes
Supports non-test configurations e.g. criterion 🏅 yes no no
Supports embedding directories at compile time no no 🏅 yes
Works with cargo-nextest 🏅 yes no 🏅 yes
Supports arbitrary function signatures 🏅 yes no no
Supports automatically injecting file contents no 🏅 yes 🏅 yes
Allows #[ignore]ing tests by glob patterns 🏅 yes 🏅 yes no

Usage

Installation

[dependencies]
fixtures = "2"

[build-dependencies]
fixtures = "2"

Setup

Add the following code to build.rs to watch your fixtures directories for changes.

fn main() {
  fixtures::build::watch_dir("path/to/fixtures");

  // or...

  fixtures::build::watch_dirs(&[
    "path/to/fixtures",
    // ...
  ]);
}

Basic Usage

use fixtures::fixtures;

#[fixtures(["fixtures/*.txt"])]
#[test]
fn test(path: &std::path::Path) {
  // This test will be run once for each file matching the glob pattern
}

The above example expands to:

use fixtures::fixtures;

#[cfg(test)]
fn test(path: &std::path::Path) {
  // This test will be run once for each file matching the glob pattern
}

#[cfg(test)]
mod test {
  use super::*;

  #[test]
  fn one_dot_txt() {
    test(::std::path::Path::new("fixtures/one.txt"));
  }

  #[test]
  fn two_dot_txt() {
    test(::std::path::Path::new("fixtures/two.txt"));
  }

  // ...

  pub const EXPANSIONS: &[fn()] = &[one_dot_txt, two_dot_txt];
}

Multiple Globs

#[fixtures(["fixtures/*.txt", "fixtures/*.data"])]
#[test]
fn test(path: &std::path::Path) {
  // This test will be run once for each file matching either "fixtures/*.txt" or "fixtures/*.data"
}

Extended Glob Syntax

fixtures supports gitignore's extended glob syntax.

use fixtures::fixtures;

#[fixtures(["fixtures/*.{txt,data}", "!fixtures/skip.*.{txt,data}"])]
#[test]
fn test(path: &std::path::Path) {
  // This test will be run once for each fixture with the extension `txt` or `data`, unless it is prefixed with `skip.`
}

Advanced Usage

Ignoring Files

Sometimes, you might want to ignore tests for one or more fixture files. If you want to skip generating the test entirely, you can simply use a negative glob, as discussed above. However, if you instead want to #[ignore] the test, you can do so as follows:

use fixtures::fixtures;

#[fixtures(
  ["fixtures/*.{txt,data}"],
  ignore = ["fixtures/ignored.txt"],
)]
#[test]
fn test(path: &std::path::Path) {
  // This test will be run once for each fixture with the extension `txt` or `data`, except for `ignored.txt` which will
  // show as "ignored" in the test output.
}

In some cases you may wish to provide a reason for ignoring the test case.

#[cfg(test)]
#[fixtures(
  ["fixtures/*.{txt,data}"],
  ignore = [
    { path = "fixtures/ignored.txt", reason = "reason for ignoring file" }
  ],
)]
#[test]
fn test(path: &std::path::Path) {}

The structure of the ignore option in EBNF notation is as follows:

Ignore                = IgnorePath | IgnorePathList | IgnoreObject ;

IgnorePathList        = "[]" | "[" IgnorePath { "," IgnorePath } [ "," ] "]" ;
IgnoreObject          = IgnorePathsOnly | IgnorePathsWithReason ;

IgnorePathsOnly       = "{" "paths" "=" (IgnorePath | IgnorePathList) [ "," ] "}" ;
IgnorePathsWithReason = "{" "paths" "=" (IgnorePath | IgnorePathList) "," "reason" "=" StringLiteral [ "," ] "}" ;

IgnorePath            = StringLiteral | IgnorePathObject ;
IgnorePathObject      = PathOnly | PathWithReason ;

PathOnly              = "{" "path" "=" StringLiteral [ "," ] "}" ;
PathWithReason        = "{" "path" "=" StringLiteral "," "reason" "=" StringLiteral [ "," ] "}" ;

StringLiteral         = /* Rust string literal */

This feature is only available for test functions; those with a #[test] attribute.

Note that the ignore glob will not be used to include files. This means that, for example, the ignore glob shown below would have no effect, since none of the files matched by the include glob, are matched by the ignore glob.

#[fixtures(
  ["*.txt"],
  ignore = ["*.txt.ignore"], // This won't work!
)]
fn test(path: &std::path::Path) {}

This can be fixed as shown in the following example.

#[fixtures(
  ["*.txt{,.ignore}"],
  ignore = ["*.txt.ignore"], // This works as expected 🥳
)]
fn test(path: &std::path::Path) {}

Criterion

fixtures can be used with criterion as shown in the following example:

#[fixtures(["fixtures/bench/*"])]
fn bench(path: &std::path::Path, c: &mut Criterion) {
  let test_name = fixture_path.file_name().unwrap().to_str().unwrap();
  c.bench_function(test_name, |b| b.iter(|| { /* ... */ }));
}

// Equivalent to criterion_group!(benches, bench::expansion_1, bench::expansion_2, ...);
fn benches() {
  let mut c = Criterion::default().configure_from_args();
  for bench in bench::EXPANSIONS {
    bench(&mut criterion);
  }
}

criterion_main!(benches);
Commit count: 44

cargo fmt