Loading...
Loading...
Testing patterns and standards for this codebase, including async effects, fakes vs mocks, and property-based testing.
npx skill4agent add neversight/skills_feed testing-patternsdyn Traituse std::time::{SystemTime, UNIX_EPOCH};
pub trait Clock {
fn now(&self) -> SystemTime;
}
pub trait Payments {
type Err;
fn charge(&self, cents: u32, card: &str) -> Result<String, Self::Err>; // returns ChargeId
}
pub struct Service<P, C> {
pay: P,
clock: C,
}
impl<P, C> Service<P, C>
where
P: Payments,
C: Clock,
{
pub fn bill(&self, card: &str, cents: u32) -> Result<String, P::Err> {
let _ts = self
.clock
.now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// domain logic… (e.g., time-based rules)
self.pay.charge(cents, card)
}
}
// --- prod adapters ---
pub struct RealClock;
impl Clock for RealClock {
fn now(&self) -> SystemTime {
SystemTime::now()
}
}
pub struct StripeClient;
impl Payments for StripeClient {
type Err = String;
fn charge(&self, cents: u32, _card: &str) -> Result<String, Self::Err> {
// call real API
Ok(format!("ch_{cents}"))
}
}
// --- test fakes ---
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::time::{Duration, SystemTime};
struct FixedClock(SystemTime);
impl Clock for FixedClock {
fn now(&self) -> SystemTime {
self.0
}
}
struct FakePayments {
pub calls: RefCell<Vec<(u32, String)>>,
pub next: RefCell<Result<String, String>>,
}
impl Payments for FakePayments {
type Err = String;
fn charge(&self, cents: u32, card: &str) -> Result<String, Self::Err> {
self.calls.borrow_mut().push((cents, card.to_string()));
self.next.borrow_mut().clone()
}
}
#[test]
fn happy_path() {
let svc = Service {
pay: FakePayments {
calls: RefCell::new(vec![]),
next: RefCell::new(Ok("ch_42".into())),
},
clock: FixedClock(SystemTime::UNIX_EPOCH + Duration::from_secs(123)),
};
let id = svc.bill("4111...", 4200).unwrap();
assert_eq!(id, "ch_42");
}
}let svc = Service { pay: StripeClient, clock: RealClock };pub struct Svc<'a> {
pay: &'a dyn Payments<Err = String>,
clock: &'a dyn Clock,
}impl Traitasync-traituse async_trait::async_trait;
#[async_trait]
pub trait Http {
async fn get(&self, url: &str) -> Result<String, anyhow::Error>;
}impl Traituse core::future::Future;
pub trait Http {
fn get(&self, url: &str) -> impl Future<Output = Result<String, anyhow::Error>> + Send;
}mockallwiremockhttpmocktempfileassert_fsArc<dyn Trait + Send + Sync>Vec<T>tests/support/mod.rsCliFixtureRemoteRepoSK_CACHE_DIRSK_CONFIG_DIRcargo llvm-cov --fail-under-lines 45mainproptesttests#[cfg(test)]
mod prop_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn fee_is_never_negative(amount in 0u64..) {
let fee = compute_fee(amount);
prop_assert!(fee >= 0);
}
}
}any::<T>()prop::collectiontests/crates/*/tests/bdreadyupdate ... --status in_progresscloseproptestprop::collectionany::<T>()from_regexfor skip in 0..5cargo testproptest!