| name | rust-mocking |
| description | Create mocks using mockall and trait-based abstractions. Use when unit testing code with external dependencies. |
Mocking
Trait-based mocking with mockall for isolated unit tests.
Setup mockall
[dev-dependencies]
mockall = "0.12"
Basic Mock with automock
use mockall::{automock, predicate::*};
#[automock]
trait Repository {
fn get(&self, id: i32) -> Option<User>;
fn save(&self, user: &User) -> Result<(), Error>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_mock() {
let mut mock = MockRepository::new();
mock.expect_get()
.with(eq(123))
.times(1)
.returning(|_| Some(User::default()));
let result = service_function(&mock, 123);
assert!(result.is_ok());
}
}
Async Mock
use mockall::{automock, predicate::*};
#[automock]
#[async_trait::async_trait]
trait AsyncRepository {
async fn get(&self, id: i32) -> Result<User, Error>;
async fn save(&self, user: &User) -> Result<(), Error>;
}
#[tokio::test]
async fn test_async_mock() {
let mut mock = MockAsyncRepository::new();
mock.expect_get()
.with(eq(42))
.returning(|_| Ok(User::default()));
let result = mock.get(42).await;
assert!(result.is_ok());
}
Predicates
use mockall::predicate::*;
mock.expect_process()
.with(eq(42))
.returning(|_| Ok(()));
mock.expect_process()
.with(ne(0))
.returning(|_| Ok(()));
mock.expect_process()
.with(gt(10))
.returning(|_| Ok(()));
mock.expect_search()
.with(str::starts_with("test"))
.returning(|_| vec![]);
mock.expect_validate()
.with(function(|x: &User| x.is_valid()))
.returning(|_| true);
mock.expect_any()
.withf(|a, b| a > b)
.returning(|_, _| true);
Return Values
mock.expect_get()
.returning(|_| Some(User::default()));
mock.expect_get()
.returning(|id| Some(User { id, ..Default::default() }));
mock.expect_get()
.times(1)
.returning(|_| Some(User::new("first")));
mock.expect_get()
.returning(|_| Some(User::new("subsequent")));
mock.expect_save()
.returning(|_| Err(Error::NotFound));
Call Counting
mock.expect_get()
.times(3)
.returning(|_| None);
mock.expect_get()
.times(1..=5)
.returning(|_| None);
mock.expect_get()
.times(1..)
.returning(|_| None);
mock.expect_get()
.times(..)
.returning(|_| None);
mock.expect_get()
.never();
Sequences
use mockall::Sequence;
let mut seq = Sequence::new();
mock.expect_connect()
.times(1)
.in_sequence(&mut seq)
.returning(|| Ok(()));
mock.expect_send()
.times(1)
.in_sequence(&mut seq)
.returning(|_| Ok(()));
mock.expect_disconnect()
.times(1)
.in_sequence(&mut seq)
.returning(|| Ok(()));
Trait-Based Design for Testability
pub trait Storage {
fn read(&self, key: &str) -> Result<Vec<u8>, Error>;
fn write(&self, key: &str, data: &[u8]) -> Result<(), Error>;
}
pub struct S3Storage {
bucket: String,
}
impl Storage for S3Storage {
fn read(&self, key: &str) -> Result<Vec<u8>, Error> {
}
fn write(&self, key: &str, data: &[u8]) -> Result<(), Error> {
}
}
pub struct Processor<S: Storage> {
storage: S,
}
impl<S: Storage> Processor<S> {
pub fn process(&, key: &) <(), Error> {
= .storage.(key)?;
.storage.(&(, key), &result)
}
}
tests {
super::*;
mockall::automock;
{ ... }
() {
= MockStorage::();
mock.()
.(())
.(|_| ([, , ]));
mock.()
.((), ())
.(|_, _| (()));
= Processor { storage: mock };
(processor.().());
}
}
Mocking with Generics
#[automock]
trait Cache<K, V> {
fn get(&self, key: &K) -> Option<V>;
fn set(&self, key: K, value: V);
}
#[test]
fn test_generic_mock() {
let mut mock = MockCache::<String, i32>::new();
mock.expect_get()
.with(eq("key".to_string()))
.returning(|_| Some(42));
assert_eq!(mock.get(&"key".to_string()), Some(42));
}
Partial Mocks with mockall_double
use mockall_double::double;
mod real_module {
pub fn helper() -> i32 { 42 }
}
#[double]
use real_module;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_mocked_helper() {
let ctx = real_module::helper_context();
ctx.expect().returning(|| 100);
assert_eq!(real_module::helper(), 100);
}
}
Guidelines
- Design with traits for testability
- Use
#[automock] for automatic mock generation
- Prefer trait bounds over concrete types in business logic
- Use predicates to match arguments
- Verify call counts with
times()
- Use sequences for order-dependent tests
- Keep mocks focused on the interface being tested
Examples
See hercules-local-algo/src/db/repo.rs for trait-based repository pattern.