Introduction

Mockall is the standard mocking library for Rust, generating mock implementations of traits for unit testing. However, it has specific limitations around generic methods, async traits, and complex return types. When mock expectations are not properly configured, tests panic with No matching expectation found or fail to compile due to unsupported trait features. Understanding Mockall's capabilities and limitations is essential for effective test-driven development in Rust.

Symptoms

  • MockXXX::method(#X): No matching expectation found panic at runtime
  • the method 'returning' exists but its trait bounds were not satisfied compilation error
  • Cannot mock generic methods with type parameters
  • Async trait methods not mocked correctly with mockall
  • Mock expectations called in wrong order causing test failure

Error output: `` thread 'tests::test_process' panicked at 'MockDatabaseClient::query(#1): No matching expectation found'

Common Causes

  • Mock expectation not set before calling the method
  • Generic method requires mock! macro instead of #[automock]
  • Return value does not match method return type
  • Async trait requires special handling with mockall
  • Multiple calls but only one expectation configured

Step-by-Step Fix

  1. 1.Basic mock setup with proper expectations:
  2. 2.```rust
  3. 3.use mockall::automock;

#[automock] trait UserRepository { fn find_by_id(&self, id: u32) -> Option<User>; fn save(&self, user: User) -> Result<User, DbError>; }

#[cfg(test)] mod tests { use super::*; use mockall::predicate::*;

#[test] fn test_find_user() { let mut mock = MockUserRepository::new();

// Set expectation BEFORE calling the method mock.expect_find_by_id() .with(eq(42)) .times(1) .returning(|id| Some(User { id, name: "Test".to_string() }));

// Now call the method let result = mock.find_by_id(42); assert!(result.is_some()); assert_eq!(result.unwrap().name, "Test"); }

#[test] #[should_panic(expected = "No matching expectation found")] fn test_unexpected_call() { let mock = MockUserRepository::new(); // No expectation set - will panic mock.find_by_id(42); } } ```

  1. 1.Mock generic methods with mock! macro:
  2. 2.```rust
  3. 3.use mockall::mock;

// automock cannot handle generic methods // #[automock] // FAILS for generic methods trait DataStore { fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T>; }

// Use mock! macro instead mock! { pub DataStore {}

impl DataStore for DataStore { fn get<T: DeserializeOwned + 'static>(&self, key: &str) -> Result<T>; } }

#[cfg(test)] mod tests { use super::*;

#[test] fn test_get_string() { let mut mock = MockDataStore::new(); mock.expect_get::<String>() .with(eq("key")) .returning(|_| Ok("value".to_string()));

let result: String = mock.get("key").unwrap(); assert_eq!(result, "value"); } } ```

  1. 1.Mock async trait methods:
  2. 2.```rust
  3. 3.use async_trait::async_trait;
  4. 4.use mockall::automock;

#[automock] #[async_trait] trait AsyncService { async fn fetch_data(&self, id: u32) -> Result<String, Error>; }

#[cfg(test)] mod tests { use super::*;

#[tokio::test] async fn test_async_fetch() { let mut mock = MockAsyncService::new();

mock.expect_fetch_data() .with(eq(1)) .returning(|_| Box::pin(async { Ok("fetched data".to_string()) }));

let result = mock.fetch_data(1).await; assert_eq!(result.unwrap(), "fetched data"); } } ```

  1. 1.Configure multiple expectations and call ordering:
  2. 2.```rust
  3. 3.#[test]
  4. 4.fn test_multiple_calls() {
  5. 5.let mut mock = MockUserRepository::new();

// Multiple calls to same method with different args mock.expect_find_by_id() .with(eq(1)) .times(1) .returning(|_| Some(User { id: 1, name: "Alice".into() }));

mock.expect_find_by_id() .with(eq(2)) .times(1) .returning(|_| Some(User { id: 2, name: "Bob".into() }));

// Sequence: specific order required let mut seq = mockall::Sequence::new();

mock.expect_save() .times(1) .in_sequence(&mut seq) .returning(|user| Ok(user));

mock.expect_find_by_id() .with(eq(1)) .times(1) .in_sequence(&mut seq) .returning(|_| Some(User { id: 1, name: "Alice".into() }));

// Calls must be in sequence order mock.save(User { id: 1, name: "Alice".into() }).unwrap(); mock.find_by_id(1); } ```

Prevention

  • Always set expectations before calling mock methods
  • Use .times(n) to verify exact call counts
  • Use mock! macro for generic methods, #[automock] for simple traits
  • Use return_const for simple return values, returning for computed values
  • Add .times(0) to assert a method was NOT called
  • Use Checkpoint to clear expectations between test cases