# Rust Testing — Derived Application > Applies Atelier's domain principles to Rust testing specifically. > Derives from `domains/` docs; introduces no new P-rules (D-063). > See `languages/rust.md` for the language first-principles stub. ## #[test] and Co-located Tests (Testing P1 Tests as Specification, C2 Clarity) - **`#[test]` on functions in a `#[cfg(test)] mod tests` block:** tests co-located with source, compiled only in `cargo test`. A test file far from its subject rots (Documentation P5 Discoverability). - **Test names read as a spec:** `fn create_user_rejects_invalid_email()` — a reader understands the unit from the name. Avoid `fn test_user_1()`. - **`assert!` / `assert_eq!` / `assert_ne!` over raw `panic!`:** the macros produce readable failure output (`assertion failed: left == right, left: 5, right: 3`). Raw `panic!` gives a message only (Testing P6 Failure Specificity). - **Applies `Testing P1`:** the test is a specification; the failure message is the spec violation. ```rust #[cfg(test)] mod tests { use super::*; #[test] fn create_user_rejects_invalid_email() { let r = create_user("not-an-email"); assert!(matches!(r, Err(Error::Validation(_)))); } #[test] fn create_user_returns_persisted_id() { let u = create_user("a@b.co").unwrap(); assert!(!u.id.is_empty()); } } ``` ## proptest and Property Tests (Testing P9 Edge Case Coverage, Testing P1 Tests as Specification) - **`proptest` (or `quickcheck`) for invariant tests:** declare a property (`parse(serialize(x)) == x`), the framework generates hundreds of inputs and shrinks failures to a minimal counterexample (Testing P9). - **Strategy over hand-written generators:** `proptest::collection::vec(any::(), 0..100)` generates arbitrary `Vec`; do not hand-roll a generator for each property. - **`proptest!` macro or `proptest! { ... }` block:** each `case (name) => { ... }` is a property. The block is the spec (Testing P1). - **Property tests complement, not replace, example tests:** examples document the happy path; properties cover the edge space. Both are required. ```rust use proptest::prelude::*; proptest! { #[test] fn roundtrips_id(s in "[a-z0-9]{1,32}") { let id = UserId::new(&s).unwrap(); assert_eq!(id.as_str(), s); } #[test] fn rejects_invalid_id(s in "[^a-z0-9]+") { assert!(UserId::new(&s).is_err()); } } ``` ## Mock Discipline (Testing P2 Independence, Testing P7 Realism) - **Mock at the trait, not the struct:** `trait Store { fn get(&self, id: &str) -> Result; }` in production; `#[automock] trait Store` (via `mockall`) in test. The trait is the contract. - **`mockall` for generated mocks:** `#[automock] trait Repo {}` generates `MockRepo` with `expect_*` methods. Each expectation is per-test; no shared mock state (Testing P2 Independence). - **Mock the boundary, not the unit:** mock `Repo`, not `UserService` (the unit). Mocking the unit under test tests the mock (Testing P7 realism). - **No `#[cfg(test)]` on production code paths to inject mocks:** instead, accept the trait as a generic or `dyn` parameter. Test-only branches in production code are dead code in prod. ```rust use mockall::*; #[automock] trait UserRepo { fn get(&self, id: &str) -> Result; } #[test] fn get_user_returns_not_found() { let mut repo = MockUserRepo::new(); repo.expect_get() .with(eq("abc")) .returning(|_| Err(Error::NotFound)); let svc = UserService::new(Box::new(repo)); assert!(matches!(svc.get_user("abc"), Err(Error::NotFound))); } ``` ## Async Tests (Concurrency P10 Test for Race Conditions, Testing P1 Tests as Specification) - **`#[tokio::test]` for `async fn` tests:** runs the coroutine on a tokio runtime. Without it, an `async fn` test returns a future, never awaited (silently passes). - **`#[tokio::test(flavor = "multi_thread")]` for concurrency-sensitive tests:** multi-thread runtime surfaces races that single-thread misses (Concurrency P10). - **`tokio::time::pause()` and `advance()` for time:** freeze and advance the runtime clock deterministically. No `tokio::time::sleep(real)` in tests. - **Race-sensitive tests use `loom` for model-checking:** `loom` simulates all thread interleavings; it catches races `-race`-style detectors miss. Use for lock-free data structures. ```rust #[tokio::test] async fn async_fetch_returns_user() { let u = fetch_user("abc").await.unwrap(); assert!(!u.name.is_empty()); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn concurrent_cache_is_safe() { let c = Arc::new(Cache::new()); let mut h = vec![]; for i in 0..10 { let c = c.clone(); h.push(tokio::spawn(async move { c.get(&i.to_string()).await; })); } for x in h { x.await.unwrap(); } } ``` ## Determinism and Time (Testing P3 Determinism, Testing P9 Edge Case Coverage) - **No `SystemTime::now()` or `Instant::now()` in code under test:** inject a `Clock` trait. In tests, a fake clock advances deterministically. - **`tokio::time::pause()` for async time:** freezes the runtime clock; `tokio::time::advance(dur)` moves it. A `sleep(5s)` in test resolves instantly. - **`--test-threads=1` to reproduce order coupling:** by default, `cargo test` runs tests in parallel; a test that passes alone but fails in a suite has hidden shared state. `-1` reproduces. ```rust trait Clock { fn now(&self) -> std::time::Instant; } struct FakeClock(std::time::Instant); impl Clock for FakeClock { fn now(&self) -> std::time::Instant { self.0 } } #[test] fn user_has_created_at() { let clk = FakeClock(std::time::Instant::now()); let u = create_user_with_clock("a@b.co", &clk).unwrap(); assert_eq!(u.created_at, clk.now()); } ``` ## Doc Tests (Documentation P1 Documentation is Code, Testing P1 Tests as Specification) - **`cargo test --doc` runs `///` fenced blocks:** a `///` example with `#`-hidden setup is a tested artifact; a stale output fails the build (Documentation P1). - **`no_run` for examples that should compile but not run:** ```` ```rust,no_run ```` — type-checks the example without executing. Use for examples that need a DB. - **`ignore` for examples that should not compile-check:** ```` ```rust,ignore ```` — skips entirely. Rare; prefer `no_run`. - **Applies `Testing P1`:** the doc example is the spec; the doc test is the spec's regression test. ```rust /// Fetch a user by id. /// /// # Example /// /// ``` /// # use mycrate::{get_user, Error}; /// let u = get_user("abc").unwrap(); /// assert!(!u.name.is_empty()); /// ``` pub fn get_user(id: &str) -> Result { /* ... */ } ``` ## Cross-References - `domains/testing/pyramid.md` — where unit/property/doc tests sit; proptest is the property layer. - `domains/testing/fixtures.md` — `t.Cleanup`-equivalent (`Drop` in tests) as fixture discipline. - `domains/testing/first-principles.md` — Testing P1 Specification, P2 Independence, P3 Determinism, P9 Edge Coverage. - `domains/concurrency/first-principles.md` — Concurrency P10 (test for races), `loom` model-checking. - `languages/rs-ownership.md` — `Send`/`Sync` tests and ownership-based property tests. - `languages/rs-async.md` — `#[tokio::test]` patterns from that doc. - `languages/rs-tooling.md` — `cargo test` flags (`--doc`, `--test-threads`) detailed here.