{"id":13675939,"url":"https://github.com/mgattozzi/assay","last_synced_at":"2026-03-07T22:32:26.892Z","repository":{"id":42506808,"uuid":"424470410","full_name":"mgattozzi/assay","owner":"mgattozzi","description":"A super powered testing macro for Rust","archived":false,"fork":false,"pushed_at":"2026-01-02T13:11:05.000Z","size":90,"stargazers_count":124,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-07T07:15:06.238Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mgattozzi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-11-04T04:21:12.000Z","updated_at":"2026-01-02T13:11:09.000Z","dependencies_parsed_at":"2022-09-13T20:50:23.632Z","dependency_job_id":null,"html_url":"https://github.com/mgattozzi/assay","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mgattozzi/assay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgattozzi%2Fassay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgattozzi%2Fassay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgattozzi%2Fassay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgattozzi%2Fassay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgattozzi","download_url":"https://codeload.github.com/mgattozzi/assay/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgattozzi%2Fassay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30234613,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T19:01:10.287Z","status":"ssl_error","status_checked_at":"2026-03-07T18:59:58.103Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-08-02T12:01:06.183Z","updated_at":"2026-03-07T22:32:26.849Z","avatar_url":"https://github.com/mgattozzi.png","language":"Rust","funding_links":[],"categories":["Crates"],"sub_categories":["Extensions"],"readme":"# assay - A super powered testing macro for Rust\n\n\u003e as·say /ˈaˌsā,aˈsā/ noun - the testing of a metal or ore to determine its ingredients and quality.\n\n`assay` is a super powered testing macro for Rust. It lets you run tests in\nparallel while also being their own process so that you can set env vars, or\ndo other per process kinds of settings without interfering with each other,\nauto mounting and changing to a tempdir, including files in it, choosing\nsetup and tear down functions, async tests, and more!\n\nRust is great, but the testing leaves much to be desired sometimes. With custom\ntest frameworks being unstable and only an eRFC since 2018 there's not much we\ncan do to expand the abilities of our tests right? Well that's where `assay`\nenters the picture. It seeks to solve a few problems when testing in rust:\n\n- Tests all run in the same process which means setting env vars or changing the\n  working dir affects all of the tests meaning you have to resort to things like\n  `cargo test -- --test-threads=1` or using some kind of mutex whereby you lose\n  the parallelization of running the test suite\n- Setting up a temporary file system to run things in for a test and having the\n  test run inside it is a pain to setup and being relative to it by using\n  `std::env::set_working_dir` is prone to the above issues\n- Including fixtures in a test, let alone multiple, can get a bit verbose\n- Setting up and tearing down the same thing for each test can be a lot\n- Want to run `async` tests? There's no runtime and you have to do setup just to\n  run it.\n- Using `?` in your test means putting `-\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e`\n  on every test and it can be tedious\n- `assert_eq`/`assert_ne` output can be hard to grok and see why something is\n  equal/not equal\n\n`assay` fixes these issues by:\n\n- Running each test as it's own process for you automatically if you use `cargo\n  test` or if you use `cargo nextest` then it let's that handle the processes\n  being in parallel in their own process for you. This means you can mutate per\n  process state as much as you want without affecting other tests and always\n  have your tests run in parallel\n- Setting per process env vars\n- Setting up a temporary directory that the test runs in (sort of like `chroot`\n  without the jail aspect and no need for `sudo` privileges)\n- Including files you want into the temp directory by specifying them\n- Letting you run async tests by simply adding `async` to the test function\n- Making all of your tests act as if they returned\n  `Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e`. Use the `?` to your hearts content\n  and no need to add the Eye of Sauron (`Ok(())`) to each test\n- Automatically importing the `pretty_assertions_sorted` crate so that you can have\n  pretty output for `assert_eq`, `assert_eq_sorted`, and `assert_ne`.\n- Allowing you to define setup and teardown functions to call for the test\n\n`assay` was born out of personal frustration with the way things are and wanting\nto handle the boilerplate without needing to write a whole test framework, while\nalso pushing the bounds of what we could have today on stable Rust.\n\n# How to use `assay`\n\nYou can get started using `assay` by importing the crate into your `Cargo.toml`'s dev\ndependencies:\n\n```toml\n[dev-dependencies]\nassay = \"0.1.0\"\n```\n\nThen importing the macro for your tests:\n\n```\n#[cfg(test)]\nuse assay::assay;\n```\n\nThis setup will by default turn on the ability for `async` tests using `tokio`, if you wish to turn\nit off to cut down on dependencies then you can do the following:\n\n```toml\n[dev-dependencies]\nassay = {version = \"0.1.0\", no-default-features = true }\n```\n\n`assay` also supports using the `async-std` runtime if you prefer instead of\n`tokio` which can be enabled as such:\n\n```toml\n[dev-dependencies]\nassay = {version = \"0.1.0\", no-default-features = true, features =\n\"async-std-runtime\" }\n```\n\n## Basic Usage \u0026 Automatic Niceties\n\nJust putting on the `#[assay]` attribute is the easiest way to get started:\n\n```rust\nuse assay::assay;\n\n#[assay]\nfn basic_usage() {\n  fs::write(\"test\", \"This is a test\")?;\n  assert_eq!(\n    \"This is a test\",\n    \u0026fs::read_to_string(\"test\")?\n  );\n}\n```\n\nThis does a few things:\n- Your test is run in a new process so that it does not have env vars or global\n  state changed between tests. This works with both `cargo nextest` and `cargo test`\n  where we fork a new process with the default `cargo test` or if you use\n  `cargo nextest` then it's already run in parallel as it's own process!\n- Is mounted in a temp directory automatically. The above example writes into\n  that directory and it's all removed on test completion.\n- Allows you to use the `?` operator inside of tests by using the catch all\n  `Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e` return value and it handles adding\n  the `Ok(())` value so you don't need to worry about that either.\n\nThis alone is great start but there's more!\n\n## Env Vars\nYou can set environment variables for each test individually. Useful if say you\nwant to test output at different log levels. The other nice thing is that since\nthese run as separate process you won't have race conditions in your test from\nwhen they are set and when you read them!\n\n```rust\nuse assay::assay;\n\n#[assay(\n  env = [\n    (\"RUST_LOG\", \"debug\"),\n    (\"OTHER\", \"value\")\n  ]\n)]\nfn debug_level() {\n  assert_eq!(env::var(\"RUST_LOG\")?, \"debug\");\n  assert_eq!(env::var(\"OTHER\")?, \"value\");\n}\n\n#[assay(\n  env = [\n    (\"RUST_LOG\", \"warn\"),\n    (\"OTHER\", \"value\")\n  ]\n)]\nfn warn_level() {\n  assert_eq!(env::var(\"RUST_LOG\")?, \"warn\");\n  assert_eq!(env::var(\"OTHER\")?, \"value\");\n}\n```\n\n## Include files\nSometimes you want to include files in your tests and generating them is one\nway, but having it in your version control system and then having them be in\nyour tests can also be nice! With the `include` directive you can include files\nin your test's directory when you start running it:\n\n```rust\nuse assay::assay;\n\n#[assay(include = [\"Cargo.toml\", \"src/lib.rs\"])]\nfn include() {\n  assert!(fs::metadata(\"src/lib.rs\")?.is_file());\n  assert!(fs::metadata(\"Cargo.toml\")?.is_file());\n}\n```\n\n## Panics\n`assay` will also let you mark a test that you expect to panic much like you\nwould for a normal Rust test:\n\n```rust\nuse assay::assay;\n\n#[assay(should_panic)]\nfn panic_test() {\n  panic!(\"Panic! At The Proc-Macro\");\n}\n```\n\n## `async` tests\nIf you want your tests to run `async` code all you need to do is specify that the\ntest is `async`. `assay` defaults to using `tokio` as the executor, but can use `async-std`.\nNote: you cannot use the `async` functionality if `no-default-features` is enabled in your\n`Cargo.toml` with no specified runtime.\n\n```rust\nuse assay::assay;\nuse std::{\n  pin::Pin,\n  future::Future,\n  task::{Poll, Context},\n};\n\n#[assay]\nasync fn async_func() {\n  ReadyOnPoll.await;\n}\n\nstruct ReadyOnPoll;\nimpl Future for ReadyOnPoll {\n  type Output = ();\n  fn poll(self: Pin\u003c\u0026mut Self\u003e, _: \u0026mut Context) -\u003e Poll\u003cSelf::Output\u003e {\n    Poll::Ready(())\n  }\n}\n```\n\n## Setup and Teardown Functions\n\nSometimes you need to setup the same things all the time and maybe with\ndifferent inputs. You might also need to handle tearing down things in the same\nway. You can define a function call expression like so with `?` support and\ndifferent parameters as input. Just define `setup` or `teardown` in your macro\nwith the function you want used before or after the test. Note\n`before_each`/`after_each` support for `assay` does not exist yet as we'd need\nsome kind of macro for the file itself to modify the args to `assay`.\n\n```rust\nuse assay::assay;\nuse std::{\n  env,\n  fs,\n  path::PathBuf,\n};\n\n#[assay(\n  setup = setup_func(5)?,\n  teardown = teardown_func(),\n)]\nfn setup_teardown_test() {\n  assert_eq!(fs::read_to_string(\"setup\")?, \"Value: 5\");\n}\n\nfn setup_func(input: i32) -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n  fs::write(\"setup\", format!(\"Value: {}\", input))?;\n  Ok(())\n}\n\nfn teardown_func() {\n  fs::remove_file(\"setup\").unwrap();\n  assert!(!PathBuf::from(\"setup\").exists());\n}\n```\n## Putting it all together!\n\nThese features can be combined as they use a comma separated list and so you\ncould do something like this:\n\n```rust\nuse assay::assay;\nuse std::{\n  env,\n  fs,\n  future::Future,\n  path::PathBuf,\n  pin::Pin,\n  task::{Poll, Context},\n};\n\n#[assay(\n  setup = setup_func(5)?,\n  env = [\n    (\"GOODBOY\", \"Bukka\"),\n    (\"BADDOGS\", \"false\")\n  ],\n  teardown = teardown_func(),\n  include = [\"Cargo.toml\", \"src/lib.rs\"],\n  should_panic,\n)]\nasync fn one_test_to_call_it_all() {\n  ReadyOnPoll.await;\n\n  assert_eq!(env::var(\"GOODBOY\")?, \"Bukka\");\n  assert_eq!(env::var(\"BADDOGS\")?, \"false\");\n  assert_eq!(fs::read_to_string(\"setup\")?, \"Value: 5\");\n  assert!(PathBuf::from(\"Cargo.toml\").exists());\n  assert!(PathBuf::from(\"src/lib.rs\").exists());\n\n  // Removing this actually causes the test to fail\n  panic!();\n}\n\nstruct ReadyOnPoll;\nimpl Future for ReadyOnPoll {\n  type Output = ();\n  fn poll(self: Pin\u003c\u0026mut Self\u003e, _: \u0026mut Context) -\u003e Poll\u003cSelf::Output\u003e {\n    Poll::Ready(())\n  }\n}\n\nfn setup_func(input: i32) -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n  fs::write(\"setup\", format!(\"Value: {}\", input))?;\n  Ok(())\n}\n\nfn teardown_func() {\n  fs::remove_file(\"setup\").unwrap();\n  assert!(!PathBuf::from(\"setup\").exists());\n}\n```\n\nUse as many or as few features as you need!\n\n# Limitations\nWhile `assay` is capable of a lot right now it's not without issues:\n- `assay` does not work inside doc tests!\n\n# MSRV Policy\nWe do not have a Minimum Supported Rust Version and only track `stable`. Older\nversions might work, but it's not guaranteed.\n\n# License\nAll files within this project are distributed under the Mozilla Public License\nversion 2.0. You can read the terms of the license [here](https://www.mozilla.org/en-US/MPL/2.0/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgattozzi%2Fassay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgattozzi%2Fassay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgattozzi%2Fassay/lists"}