| Crates.io | fixtures |
| lib.rs | fixtures |
| version | 2.3.2 |
| created_at | 2023-09-17 16:26:32.178834+00 |
| updated_at | 2025-09-21 23:49:22.689604+00 |
| description | Run tests against fixtures |
| homepage | https://github.com/bcheidemann/fixtures-rs |
| repository | https://github.com/bcheidemann/fixtures-rs |
| max_upload_size | |
| id | 975213 |
| size | 42,337 |
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.
#[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");
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 |
[dependencies]
fixtures = "2"
[build-dependencies]
fixtures = "2"
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",
// ...
]);
}
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];
}
#[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"
}
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.`
}
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) {}
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);