Agent skill

mock-strategy-guide

Guides users on creating mock implementations for testing with traits, providing test doubles, and avoiding tight coupling to test infrastructure. Activates when users need to test code with external dependencies.

Stars 232
Forks 15

Install this agent skill to your Project

npx add-skill https://github.com/aiskillstore/marketplace/tree/main/skills/emillindfors/mock-strategy-guide

SKILL.md

Mock Strategy Guide Skill

You are an expert at testing strategies for Rust, especially creating mock implementations for hexagonal architecture. When you detect testing needs for code with dependencies, proactively suggest mocking strategies.

When to Activate

Activate when you notice:

  • Code with external dependencies (DB, HTTP, etc.)
  • Trait-based abstractions for repositories or services
  • Tests that require real infrastructure
  • Questions about mocking or test doubles

Mock Implementation Patterns

Pattern 1: Simple Mock Repository

rust
#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    struct MockUserRepository {
        users: HashMap<String, User>,
    }

    impl MockUserRepository {
        fn new() -> Self {
            Self {
                users: HashMap::new(),
            }
        }

        fn with_user(mut self, user: User) -> Self {
            self.users.insert(user.id.clone(), user);
            self
        }
    }

    #[async_trait]
    impl UserRepository for MockUserRepository {
        async fn find(&self, id: &str) -> Result<User, Error> {
            self.users
                .get(id)
                .cloned()
                .ok_or(Error::NotFound)
        }

        async fn save(&self, user: &User) -> Result<(), Error> {
            // Mock just succeeds
            Ok(())
        }
    }

    #[tokio::test]
    async fn test_user_service() {
        // Arrange
        let user = User { id: "1".to_string(), email: "test@example.com".to_string() };
        let mock_repo = MockUserRepository::new().with_user(user.clone());
        let service = UserService::new(mock_repo);

        // Act
        let result = service.get_user("1").await;

        // Assert
        assert!(result.is_ok());
        assert_eq!(result.unwrap().id, "1");
    }
}

Pattern 2: Mock with Verification

rust
#[cfg(test)]
mod tests {
    use std::sync::{Arc, Mutex};

    struct MockEmailService {
        sent_emails: Arc<Mutex<Vec<Email>>>,
    }

    impl MockEmailService {
        fn new() -> Self {
            Self {
                sent_emails: Arc::new(Mutex::new(Vec::new())),
            }
        }

        fn emails_sent(&self) -> Vec<Email> {
            self.sent_emails.lock().unwrap().clone()
        }
    }

    #[async_trait]
    impl EmailService for MockEmailService {
        async fn send(&self, email: Email) -> Result<(), Error> {
            self.sent_emails.lock().unwrap().push(email);
            Ok(())
        }
    }

    #[tokio::test]
    async fn test_sends_welcome_email() {
        let mock_email = MockEmailService::new();
        let service = UserService::new(mock_email.clone());

        service.register_user("test@example.com").await.unwrap();

        // Verify email was sent
        let emails = mock_email.emails_sent();
        assert_eq!(emails.len(), 1);
        assert_eq!(emails[0].to, "test@example.com");
        assert!(emails[0].subject.contains("Welcome"));
    }
}

Pattern 3: Mock with Controlled Failures

rust
#[cfg(test)]
mod tests {
    enum MockBehavior {
        Success,
        NotFound,
        DatabaseError,
    }

    struct MockRepository {
        behavior: MockBehavior,
    }

    impl MockRepository {
        fn with_behavior(behavior: MockBehavior) -> Self {
            Self { behavior }
        }
    }

    #[async_trait]
    impl UserRepository for MockRepository {
        async fn find(&self, id: &str) -> Result<User, Error> {
            match self.behavior {
                MockBehavior::Success => Ok(test_user()),
                MockBehavior::NotFound => Err(Error::NotFound),
                MockBehavior::DatabaseError => Err(Error::Database("Connection failed".into())),
            }
        }
    }

    #[tokio::test]
    async fn test_handles_not_found() {
        let mock = MockRepository::with_behavior(MockBehavior::NotFound);
        let service = UserService::new(mock);

        let result = service.get_user("1").await;

        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), Error::NotFound));
    }

    #[tokio::test]
    async fn test_handles_database_error() {
        let mock = MockRepository::with_behavior(MockBehavior::DatabaseError);
        let service = UserService::new(mock);

        let result = service.get_user("1").await;

        assert!(result.is_err());
    }
}

Pattern 4: Builder Pattern for Mocks

rust
#[cfg(test)]
mod tests {
    struct MockRepositoryBuilder {
        users: HashMap<String, User>,
        find_error: Option<Error>,
        save_error: Option<Error>,
    }

    impl MockRepositoryBuilder {
        fn new() -> Self {
            Self {
                users: HashMap::new(),
                find_error: None,
                save_error: None,
            }
        }

        fn with_user(mut self, user: User) -> Self {
            self.users.insert(user.id.clone(), user);
            self
        }

        fn with_find_error(mut self, error: Error) -> Self {
            self.find_error = Some(error);
            self
        }

        fn build(self) -> MockRepository {
            MockRepository {
                users: self.users,
                find_error: self.find_error,
                save_error: self.save_error,
            }
        }
    }

    #[tokio::test]
    async fn test_with_builder() {
        let mock = MockRepositoryBuilder::new()
            .with_user(test_user())
            .with_save_error(Error::Database("Save failed".into()))
            .build();

        let service = UserService::new(mock);

        // Can find user
        let user = service.get_user("1").await.unwrap();

        // But save fails
        let result = service.update_user(user).await;
        assert!(result.is_err());
    }
}

In-Memory Test Implementations

For integration tests with real logic but no infrastructure:

rust
pub struct InMemoryUserRepository {
    users: Arc<Mutex<HashMap<String, User>>>,
}

impl InMemoryUserRepository {
    pub fn new() -> Self {
        Self {
            users: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}

#[async_trait]
impl UserRepository for InMemoryUserRepository {
    async fn find(&self, id: &str) -> Result<User, Error> {
        self.users
            .lock()
            .unwrap()
            .get(id)
            .cloned()
            .ok_or(Error::NotFound)
    }

    async fn save(&self, user: &User) -> Result<(), Error> {
        self.users
            .lock()
            .unwrap()
            .insert(user.id.clone(), user.clone());
        Ok(())
    }

    async fn delete(&self, id: &str) -> Result<(), Error> {
        self.users
            .lock()
            .unwrap()
            .remove(id)
            .ok_or(Error::NotFound)?;
        Ok(())
    }
}

Test Fixture Helpers

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

    pub fn test_user() -> User {
        User {
            id: "test-id".to_string(),
            email: "test@example.com".to_string(),
            name: "Test User".to_string(),
        }
    }

    pub fn test_user_with_id(id: &str) -> User {
        User {
            id: id.to_string(),
            email: "test@example.com".to_string(),
            name: "Test User".to_string(),
        }
    }

    pub fn test_users(count: usize) -> Vec<User> {
        (0..count)
            .map(|i| test_user_with_id(&format!("user-{}", i)))
            .collect()
    }
}

Your Approach

When you see code needing tests:

  1. Identify external dependencies (traits)
  2. Suggest mock implementation structure
  3. Show verification patterns
  4. Provide test fixture helpers

When you see tests without mocks:

  1. Suggest extracting trait if tightly coupled
  2. Show how to create mock implementations
  3. Demonstrate verification patterns

Proactively suggest mocking strategies for testable, maintainable code.

Expand your agent's capabilities with these related and highly-rated skills.

aiskillstore/marketplace

perigon-backend

Perigon ASP.NET Core + EF Core + Aspire conventions

232 15
Explore
aiskillstore/marketplace

perigon-agent

Pointers for Copilot/agents to apply Perigon conventions

232 15
Explore
aiskillstore/marketplace

perigon-angular

Angular 21+ standalone/Material/signal conventions for Perigon WebApp

232 15
Explore
aiskillstore/marketplace

fastapi-mastery

Comprehensive FastAPI development skill covering REST API creation, routing, request/response handling, validation, authentication, database integration, middleware, and deployment. Use when working with FastAPI projects, building APIs, implementing CRUD operations, setting up authentication/authorization, integrating databases (SQL/NoSQL), adding middleware, handling WebSockets, or deploying FastAPI applications. Triggered by requests involving .py files with FastAPI code, API endpoint creation, Pydantic models, or FastAPI-specific features.

232 15
Explore
aiskillstore/marketplace

context7-efficient

Token-efficient library documentation fetcher using Context7 MCP with 86.8% token savings through intelligent shell pipeline filtering. Fetches code examples, API references, and best practices for JavaScript, Python, Go, Rust, and other libraries. Use when users ask about library documentation, need code examples, want API usage patterns, are learning a new framework, need syntax reference, or troubleshooting with library-specific information. Triggers include questions like "Show me React hooks", "How do I use Prisma", "What's the Next.js routing syntax", or any request for library/framework documentation.

232 15
Explore
aiskillstore/marketplace

browser-use

Browser automation using Playwright MCP. Navigate websites, fill forms, click elements, take screenshots, and extract data. Use when tasks require web browsing, form submission, web scraping, UI testing, or any browser interaction.

232 15
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results