{"id":13599821,"url":"https://github.com/la10736/rstest","last_synced_at":"2025-05-12T13:30:20.436Z","repository":{"id":39613474,"uuid":"124802455","full_name":"la10736/rstest","owner":"la10736","description":"Fixture-based test framework for Rust","archived":false,"fork":false,"pushed_at":"2025-04-22T13:45:20.000Z","size":2171,"stargazers_count":1331,"open_issues_count":63,"forks_count":56,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-23T17:11:25.915Z","etag":null,"topics":["rust","test-framework","testing","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/la10736.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-03-11T21:46:36.000Z","updated_at":"2025-04-23T08:51:11.000Z","dependencies_parsed_at":"2023-12-17T11:31:55.104Z","dependency_job_id":"44b68423-a8af-485a-95f0-c25b53a1471f","html_url":"https://github.com/la10736/rstest","commit_stats":{"total_commits":716,"total_committers":16,"mean_commits":44.75,"dds":"0.25418994413407825","last_synced_commit":"4378e0e39576d4ca1197f1acb9ead1117ba4d581"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/la10736%2Frstest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/la10736%2Frstest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/la10736%2Frstest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/la10736%2Frstest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/la10736","download_url":"https://codeload.github.com/la10736/rstest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253747596,"owners_count":21957787,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust","test-framework","testing","testing-tools"],"created_at":"2024-08-01T17:01:12.507Z","updated_at":"2025-05-12T13:30:20.330Z","avatar_url":"https://github.com/la10736.png","language":"Rust","funding_links":[],"categories":["Rust","Crates","Development tools"],"sub_categories":["Harnesses/Frameworks","Testing"],"readme":"[![Crate][crate-image]][crate-link]\n[![Docs][docs-image]][docs-link]\n[![Status][test-action-image]][test-action-link]\n[![Apache 2.0 Licensed][license-apache-image]][license-apache-link]\n[![MIT Licensed][license-mit-image]][license-mit-link]\n\n# Fixture-based test framework for Rust\n\n## Introduction\n\n`rstest` uses procedural macros to help you on writing\nfixtures and table-based tests. To use it, add the\nfollowing lines to your `Cargo.toml` file:\n\n```toml\n[dev-dependencies]\nrstest = \"0.25.0\"\n```\n\n### Features\n\n- `async-timeout`: `timeout` for `async` tests (Default enabled)\n- `crate-name`: Import `rstest` package with different name (Default enabled)\n\n### Fixture\n\nThe core idea is that you can inject your test dependencies\nby passing them as test arguments. In the following example,\na `fixture` is defined and then used in two tests,\nsimply providing it as an argument:\n\n```rust\nuse rstest::*;\n\n#[fixture]\npub fn fixture() -\u003e u32 { 42 }\n\n#[rstest]\nfn should_success(fixture: u32) {\n        assert_eq!(fixture, 42);\n}\n\n#[rstest]\nfn should_fail(fixture: u32) {\n        assert_ne!(fixture, 42);\n}\n```\n\n### Parametrize\n\nYou can also inject values in some other ways. For instance, you can\ncreate a set of tests by simply providing the injected values for each\ncase: `rstest` will generate an independent test for each case.\n\n```rust\nuse rstest::rstest;\n\n#[rstest]\n#[case(0, 0)]\n#[case(1, 1)]\n#[case(2, 1)]\n#[case(3, 2)]\n#[case(4, 3)]\nfn fibonacci_test(#[case] input: u32, #[case] expected: u32) {\n    assert_eq!(expected, fibonacci(input))\n}\n```\n\nRunning `cargo test` in this case executes five tests:\n\n```bash\nrunning 5 tests\ntest fibonacci_test::case_1 ... ok\ntest fibonacci_test::case_2 ... ok\ntest fibonacci_test::case_3 ... ok\ntest fibonacci_test::case_4 ... ok\ntest fibonacci_test::case_5 ... ok\n\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\nIf you need to just providing a bunch of values for which you\nneed to run your test, you can use `#[values(list, of, values)]`\nargument attribute:\n\n```rust\nuse rstest::rstest;\n\n#[rstest]\nfn should_be_invalid(\n    #[values(None, Some(\"\"), Some(\"    \"))]\n    value: Option\u003c\u0026str\u003e\n) {\n    assert!(!valid(value))\n}\n```\n\nOr create a _matrix_ test by using _list of values_ for some\nvariables that will generate the cartesian product of all the\nvalues.\n\n#### Use Parametrize definition in more tests\n\nIf you need to use a test list for more than one test you can use [`rstest_reuse`][reuse-crate-link]\ncrate. With this helper crate you can define a template and use it everywhere.\n\n```rust\nuse rstest::rstest;\nuse rstest_reuse::{self, *};\n\n#[template]\n#[rstest]\n#[case(2, 2)]\n#[case(4/2, 2)]\nfn two_simple_cases(#[case] a: u32, #[case] b: u32) {}\n\n#[apply(two_simple_cases)]\nfn it_works(#[case] a: u32, #[case] b: u32) {\n    assert!(a == b);\n}\n```\n\nSee [`rstest_reuse`][reuse-crate-link] for more details.\n\n#### Feature flagged cases\n\nIn case you want certain test cases to only be present if a certain feature is\nenabled, use `#[cfg_attr(feature = …, case(…))]`:\n\n```rust\nuse rstest::rstest;\n\n#[rstest]\n#[case(2, 2)]\n#[cfg_attr(feature = \"frac\", case(4/2, 2))]\nfn it_works(#[case] a: u32, #[case] b: u32) {\n    assert!(a == b);\n}\n```\n\nThis also works with [`rstest_reuse`][reuse-crate-link].\n\n### Magic Conversion\n\nIf you need a value where its type implement `FromStr()` trait you can use a literal\nstring to build it:\n\n```rust\n# use rstest::rstest;\n# use std::net::SocketAddr;\n#[rstest]\n#[case(\"1.2.3.4:8080\", 8080)]\n#[case(\"127.0.0.1:9000\", 9000)]\nfn check_port(#[case] addr: SocketAddr, #[case] expected: u16) {\n    assert_eq!(expected, addr.port());\n}\n```\n\nYou can use this feature also in value list and in fixture default value.\n\n### Async\n\n`rstest` provides out of the box `async` support. Just mark your\ntest function as `async`, and it'll use `#[async-std::test]` to\nannotate it. This feature can be really useful to build async\nparametric tests using a tidy syntax:\n\n```rust\nuse rstest::*;\n\n#[rstest]\n#[case(5, 2, 3)]\n#[should_panic]\n#[case(42, 40, 1)]\nasync fn my_async_test(#[case] expected: u32, #[case] a: u32, #[case] b: u32) {\n    assert_eq!(expected, async_sum(a, b).await);\n}\n```\n\nCurrently, only `async-std` is supported out of the box. But if you need to use\nanother runtime that provide its own test attribute (i.e. `tokio::test` or\n`actix_rt::test`) you can use it in your `async` test like described in\n[Inject Test Attribute](#inject-test-attribute).\n\nTo use this feature, you need to enable `attributes` in the `async-std`\nfeatures list in your `Cargo.toml`:\n\n```toml\nasync-std = { version = \"1.13\", features = [\"attributes\"] }\n```\n\nIf your test input is an async value (fixture or test parameter) you can use `#[future]`\nattribute to remove `impl Future\u003cOutput = T\u003e` boilerplate and just use `T`:\n\n```rust\nuse rstest::*;\n#[fixture]\nasync fn base() -\u003e u32 { 42 }\n\n#[rstest]\n#[case(21, async { 2 })]\n#[case(6, async { 7 })]\nasync fn my_async_test(#[future] base: u32, #[case] expected: u32, #[future]\n#[case] div: u32) {\n    assert_eq!(expected, base.await / div.await);\n}\n```\n\nAs you noted you should `.await` all _future_ values and this sometimes can be really boring.\nIn this case you can use `#[future(awt)]` to _awaiting_ an input or annotating your function\nwith `#[awt]` attributes to globally `.await` all your _future_ inputs. Previous code can be\nsimplified like follow:\n\n```rust\nuse rstest::*;\n# #[fixture]\n# async fn base() -\u003e u32 { 42 }\n#[rstest]\n#[case(21, async { 2 })]\n#[case(6, async { 7 })]\n#[awt]\nasync fn global(#[future] base: u32, #[case] expected: u32, #[future]\n#[case] div: u32) {\n    assert_eq!(expected, base / div);\n}\n#[rstest]\n#[case(21, async { 2 })]\n#[case(6, async { 7 })]\nasync fn single(#[future] base: u32, #[case] expected: u32, #[future(awt)]\n#[case] div: u32) {\n    assert_eq!(expected, base.await / div);\n}\n```\n\n### Files path as input arguments\n\nIf you need to create a test for each file in a given location you can use\n`#[files(\"glob path syntax\")]` attribute to generate a test for each file that\nsatisfy the given glob path.\n\n```rust\n#[rstest]\nfn for_each_file(#[files(\"src/**/*.rs\")]\n                 #[exclude(\"test\")] path: PathBuf) {\n    assert!(check_file(\u0026path))\n}\n```\n\nThe default behavior is to ignore the files that start with `\".\"`, but you can\nmodify this by use `#[include_dot_files]` attribute. The `files` attribute can be\nused more than once on the same variable, and you can also create some custom\nexclusion rules with the `#[exclude(\"regex\")]` attributes that filter out all\npaths that verify the regular expression.\n\nYou can pass in environment variables by using `${ENV_VAR_NAME}` in the glob\npath, e.g. `#[files(\"${SOME_ENV}/hello\")]`. To set a default value for the\nenvironment variable, use `${ENV_VAR_NAME:-default_value}`.\n\nFiles are resolved at compile time against your Cargo project root\n(the `CARGO_MANIFEST_DIR` environment variable). If you need to change this\nbehavior, you can use the `#[base_dir = \"...\"]` attribute to specify a different\nbase directory. That directory MUST exist, and will be used as the root for\nthe files, as well as to resolve the relative path when creating the test name.\nSimilar to the `files` attribute, you can use `${ENV_VAR_NAME}` in the `base_dir`.\n\nThe `#[files(...)]` attribute ignores matched directory paths by default,\nreturning only file paths. If you need the test cases to include directories\nfound by the glob pattern, use the `#[dirs]` attribute in conjunction with `#[files(...)]`.\n\n### Use `#[once]` Fixture\n\nIf you need to a fixture that should be initialized just once for all tests\nyou can use `#[once]` attribute. `rstest` call your fixture function just once and\nreturn a reference to your function result to all your tests:\n\n```rust\n#[fixture]\n#[once]\nfn once_fixture() -\u003e i32 { 42 }\n\n#[rstest]\nfn single(once_fixture: \u0026i32) {\n    // All tests that use once_fixture will share the same reference to once_fixture() \n    // function result.\n    assert_eq!(\u002642, once_fixture)\n}\n```\n\n### Test `#[timeout()]`\n\nYou can define an execution timeout for your tests with `#[timeout(\u003cduration\u003e)]` attribute. Timeout\nworks both for sync and async tests and is runtime agnostic. `#[timeout(\u003cduration\u003e)]` take an\nexpression that should return a `std::time::Duration`. Follow a simple async example:\n\n```rust\nuse rstest::*;\nuse std::time::Duration;\n\nasync fn delayed_sum(a: u32, b: u32, delay: Duration) -\u003e u32 {\n    async_std::task::sleep(delay).await;\n    a + b\n}\n\n#[rstest]\n#[timeout(Duration::from_millis(80))]\nasync fn single_pass() {\n    assert_eq!(4, delayed_sum(2, 2, ms(10)).await);\n}\n```\n\nIn this case test pass because the delay is just 10 milliseconds and timeout is\n80 milliseconds.\n\nYou can use `timeout` attribute like any other attribute in your tests, and you can\noverride a group timeout with a case specific one. In the follow example we have\n3 tests where first and third use 100 milliseconds but the second one use 10 milliseconds.\nAnother valuable point in this example is to use an expression to compute the\nduration.\n\n```rust\nfn ms(ms: u32) -\u003e Duration {\n    Duration::from_millis(ms.into())\n}\n\n#[rstest]\n#[case::pass(ms(1), 4)]\n#[timeout(ms(10))]\n#[case::fail_timeout(ms(60), 4)]\n#[case::fail_value(ms(1), 5)]\n#[timeout(ms(100))]\nasync fn group_one_timeout_override(#[case] delay: Duration, #[case] expected: u32) {\n    assert_eq!(expected, delayed_sum(2, 2, delay).await);\n}\n```\n\nIf you want to use `timeout` for `async` test you need to use `async-timeout`\nfeature (enabled by default).\n\n### Default timeout\n\nYou can set a default timeout for test using the `RSTEST_TIMEOUT` environment variable.\nThe value is in seconds and is evaluated on test compile time.\n\n### Inject Test Attribute\n\nIf you would like to use another `test` attribute for your test you can simply\nindicate it in your test function's attributes. For instance if you want\nto test some async function with use `actix_rt::test` attribute you can just write:\n\n```rust\nuse rstest::*;\nuse actix_rt;\nuse std::future::Future;\n\n#[rstest]\n#[case(2, async { 4 })]\n#[case(21, async { 42 })]\n#[actix_rt::test]\nasync fn my_async_test(#[case] a: u32, #[case]\n#[future] result: u32) {\n    assert_eq!(2 * a, result.await);\n}\n```\n\nJust the attributes that ends with `test` (last path segment) can be injected.\n\n## Test `Context` object\n\nYou can have a [`Context`] object for your test just by annotate an argument by `#[context]` attribute.\nThis object contains some useful information both to implement simple logics and debugging stuff.  \n\n```rust\nuse rstest::{rstest, Context};\n\n#[rstest]\n#[case::a_description(42)]\nfn my_test(#[context] ctx: Context, #[case] _c: u32) {\n    assert_eq!(\"my_test\", ctx.name);\n    assert_eq!(Some(\"a_description\"), ctx.description);\n    assert_eq!(Some(0), ctx.case);\n\n    std::thread::sleep(std::time::Duration::from_millis(100));\n    assert!(ctx.start.elapsed() \u003e= std::time::Duration::from_millis(100));\n}\n```\n\n## Local lifetime and `#[by_ref]` attribute\n\nIn some cases you may want to use a local lifetime for some arguments of your test.\nIn these cases you can use the `#[by_ref]` attribute then use the reference instead\nthe value.\n\n```rust\nenum E\u003c'a\u003e {\n    A(bool),\n    B(\u0026'a Cell\u003cE\u003c'a\u003e\u003e),\n}\n\nfn make_e_from_bool\u003c'a\u003e(_bump: \u0026'a (), b: bool) -\u003e E\u003c'a\u003e {\n    E::A(b)\n}\n\n#[fixture]\nfn bump() -\u003e () {}\n\n#[rstest]\n#[case(true, E::A(true))]\nfn it_works\u003c'a\u003e(#[by_ref] bump: \u0026'a (), #[case] b: bool, #[case] expected: E\u003c'a\u003e) {\n    let actual = make_e_from_bool(\u0026bump, b);\n    assert_eq!(actual, expected);\n}\n```\n\nYou can use `#[by_ref]` attribute for all arguments of your test and not just for fixture\nbut also for cases, values and files.\n\n## Complete Example\n\nAll these features can be used together with a mixture of fixture variables,\nfixed cases and a bunch of values. For instance, you might need two\ntest cases which test for panics, one for a logged-in user and one for a guest user.\n\n```rust\nuse rstest::*;\n\n#[fixture]\nfn repository() -\u003e InMemoryRepository {\n    let mut r = InMemoryRepository::default();\n    // fill repository with some data\n    r\n}\n\n#[fixture]\nfn alice() -\u003e User {\n    User::logged(\"Alice\", \"2001-10-04\", \"London\", \"UK\")\n}\n\n#[rstest]\n#[case::authorized_user(alice())] // We can use `fixture` also as standard function\n#[case::guest(User::Guest)]   // We can give a name to every case : `guest` in this case\n// and `authorized_user`\n#[should_panic(expected = \"Invalid query error\")] // We would test a panic\nfn should_be_invalid_query_error(\n    repository: impl Repository,\n    #[case] user: User,\n    #[values(\"     \", \"^%$some#@invalid!chars\", \".n.o.d.o.t.s.\")] query: \u0026str,\n) {\n    repository.find_items(\u0026user, query).unwrap();\n}\n```\n\nThis example will generate exactly 6 tests grouped by 2 different cases:\n\n```text\nrunning 6 tests\ntest should_be_invalid_query_error::case_1_authorized_user::query_1_____ - should panic ... ok\ntest should_be_invalid_query_error::case_2_guest::query_2_____someinvalid_chars__ - should panic ... ok\ntest should_be_invalid_query_error::case_1_authorized_user::query_2_____someinvalid_chars__ - should panic ... ok\ntest should_be_invalid_query_error::case_2_guest::query_3____n_o_d_o_t_s___ - should panic ... ok\ntest should_be_invalid_query_error::case_1_authorized_user::query_3____n_o_d_o_t_s___ - should panic ... ok\ntest should_be_invalid_query_error::case_2_guest::query_1_____ - should panic ... ok\n\ntest result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n```\n\nNote that the names of the values _try_ to convert the input expression in a\nRust valid identifier name to help you find which tests fail.\n\n## More\n\nIs that all? Not quite yet!\n\nA fixture can be injected by another fixture, and they can be called\nusing just some of its arguments.\n\n```rust\n#[fixture]\nfn user(#[default(\"Alice\")] name: \u0026str, #[default(22)] age: u8) -\u003e User {\n    User::new(name, age)\n}\n\n#[rstest]\nfn is_alice(user: User) {\n    assert_eq!(user.name(), \"Alice\")\n}\n\n#[rstest]\nfn is_22(user: User) {\n    assert_eq!(user.age(), 22)\n}\n\n#[rstest]\nfn is_bob(#[with(\"Bob\")] user: User) {\n    assert_eq!(user.name(), \"Bob\")\n}\n\n#[rstest]\nfn is_42(#[with(\"\", 42)] user: User) {\n    assert_eq!(user.age(), 42)\n}\n```\n\nAs you noted you can provide default values without the need of a fixture\nto define it.\n\nFinally, if you need tracing the input values you can just\nadd the `trace` attribute to your test to enable the dump of all input\nvariables.\n\n```rust\n#[rstest]\n#[case(42, \"FortyTwo\", (\"minus twelve\", -12))]\n#[case(24, \"TwentyFour\", (\"minus twentyfour\", -24))]\n#[trace] //This attribute enable tracing\nfn should_fail(#[case] number: u32, #[case] name: \u0026str, #[case] tuple: (\u0026str, i32)) {\n    assert!(false); // \u003c- stdout come out just for failed tests\n}\n```\n\n```text\nrunning 2 tests\ntest should_fail::case_1 ... FAILED\ntest should_fail::case_2 ... FAILED\n\nfailures:\n\n---- should_fail::case_1 stdout ----\n------------ TEST ARGUMENTS ------------\nnumber = 42\nname = \"FortyTwo\"\ntuple = (\"minus twelve\", -12)\n-------------- TEST START --------------\nthread 'should_fail::case_1' panicked at 'assertion failed: false', src/main.rs:64:5\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n\n---- should_fail::case_2 stdout ----\n------------ TEST ARGUMENTS ------------\nnumber = 24\nname = \"TwentyFour\"\ntuple = (\"minus twentyfour\", -24)\n-------------- TEST START --------------\nthread 'should_fail::case_2' panicked at 'assertion failed: false', src/main.rs:64:5\n\n\nfailures:\n    should_fail::case_1\n    should_fail::case_2\n\ntest result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out\n```\n\nIn case one or more variables don't implement the `Debug` trait, an error\nis raised, but it's also possible to exclude a variable using the\n`#[notrace]` argument attribute.\n\nYou can learn more on [Docs][docs-link] and find more examples in\n[`tests/resources`](/rstest/tests/resources) directory.\n\n## Rust version compatibility\n\nThe minimum supported Rust version is 1.67.1.\n\n## Changelog\n\nSee [CHANGELOG.md](/CHANGELOG.md)\n\n## License\n\nLicensed under either of\n\n* Apache License, Version 2.0, ([LICENSE-APACHE](/LICENSE-APACHE) or\n  [license-apache-link])\n\n* MIT license [LICENSE-MIT](/LICENSE-MIT) or [license-MIT-link]\n  at your option.\n\n[//]: # (links)\n\n[crate-image]: https://img.shields.io/crates/v/rstest.svg\n\n[crate-link]: https://crates.io/crates/rstest\n\n[docs-image]: https://docs.rs/rstest/badge.svg\n\n[docs-link]: https://docs.rs/rstest/\n\n[test-action-image]: https://github.com/la10736/rstest/workflows/Test/badge.svg\n\n[test-action-link]: https://github.com/la10736/rstest/actions?query=workflow:Test\n\n[license-apache-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg\n\n[license-mit-image]: https://img.shields.io/badge/license-MIT-blue.svg\n\n[license-apache-link]: http://www.apache.org/licenses/LICENSE-2.0\n\n[license-MIT-link]: http://opensource.org/licenses/MIT\n\n[reuse-crate-link]: https://crates.io/crates/rstest_reuse\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fla10736%2Frstest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fla10736%2Frstest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fla10736%2Frstest/lists"}