testing

varubogu's avatarfrom varubogu

Test creation and mocking (mockall, unit tests, integration tests, cargo test, AAA pattern). Use when writing tests, creating mocks, running cargo test, debugging test failures, asking about testing strategies, mockall usage, or test coverage.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive framework for Rust software testing, specializing in unit and integration test creation, advanced mocking with the mockall library, and the implementation of the Arrange-Act-Assert (AAA) pattern. It streamlines the quality assurance process by offering standardized templates for async testing with Tokio and efficient debugging strategies using cargo test to ensure high code coverage and reliability.

Use Cases

  • Case 1: Generating unit tests for Rust modules using #[cfg(test)] to validate isolated business logic.
  • Case 2: Creating sophisticated mock objects for traits using mockall to simulate database transactions or external service dependencies.
  • Case 3: Implementing integration tests in the tests/ directory to verify end-to-end system workflows and API interactions.
  • Case 4: Refactoring test suites to follow the AAA (Arrange-Act-Assert) pattern for improved readability and maintainability.
  • Case 5: Troubleshooting asynchronous code failures and performance issues using tokio-test and specialized cargo test commands.
nametesting
descriptionTest creation and mocking (mockall, unit tests, integration tests, cargo test, AAA pattern). Use when writing tests, creating mocks, running cargo test, debugging test failures, asking about testing strategies, mockall usage, or test coverage.

Testing Guide

Tech Stack

  • Test Framework: Standard #[test] / #[tokio::test]
  • Mocking: mockall
  • Async Testing: tokio-test

Test Structure

Unit Tests

Place in modules with #[cfg(test)]

// src/services/user_service.rs
pub struct UserService {
    repository: Arc<dyn UserRepository>,
}

impl UserService {
    pub async fn create(&self, data: UserData) -> Result<User> {
        // Implementation
    }
}

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

    #[tokio::test]
    async fn test_create_user_success() {
        // Test code
    }
}

Integration Tests

Place in tests/ directory

// tests/integration_test.rs
use my_app::prelude::*;

#[tokio::test]
async fn test_full_workflow() {
    // Integration test code
}

AAA Pattern

Arrange-Act-Assert pattern recommended

#[tokio::test]
async fn test_create_user_success() {
    // Arrange: Setup
    let mut mock_repository = MockUserRepository::new();
    mock_repository
        .expect_create_with_txn()
        .returning(|_, data| Ok(User {
            id: 1,
            name: data.name,
            email: data.email,
        }));

    let service = UserService::new(Arc::new(mock_repository));
    let user_data = UserData {
        name: "太郎".to_string(),
        email: "taro@example.com".to_string(),
    };

    // Act: Execute
    let result = service.create(user_data).await;

    // Assert: Verify
    assert!(result.is_ok());
    let user = result.unwrap();
    assert_eq!(user.name, "太郎");
    assert_eq!(user.email, "taro@example.com");
}

Mocking (mockall)

Trait Definition

use mockall::automock;

#[automock]
pub trait UserRepository: Send + Sync {
    async fn create_with_txn(
        &self,
        txn: &DatabaseTransaction,
        data: UserData,
    ) -> Result<User, RepositoryError>;

    async fn find_by_id(
        &self,
        txn: &DatabaseTransaction,
        id: i32,
    ) -> Result<Option<User>, RepositoryError>;
}

Using Mocks

#[tokio::test]
async fn test_with_mock() {
    // Create mock
    let mut mock_repo = MockUserRepository::new();

    // Set expectations
    mock_repo
        .expect_create_with_txn()
        .times(1)  // Called once
        .with(eq(txn), eq(user_data))  // Argument verification
        .returning(|_, data| Ok(User {
            id: 1,
            name: data.name,
            email: data.email,
        }));

    // Run test
    let service = UserService::new(Arc::new(mock_repo));
    let result = service.create(user_data).await;

    assert!(result.is_ok());
}

Multiple Call Mocks

#[tokio::test]
async fn test_multiple_calls() {
    let mut mock_repo = MockUserRepository::new();

    // Set multiple expectations
    mock_repo
        .expect_find_by_id()
        .times(2)
        .returning(|_, id| {
            if id == 1 {
                Ok(Some(User { id: 1, name: "太郎".to_string() }))
            } else {
                Ok(None)
            }
        });

    let service = UserService::new(Arc::new(mock_repo));

    let user1 = service.find_by_id(1).await.unwrap();
    assert!(user1.is_some());

    let user2 = service.find_by_id(2).await.unwrap();
    assert!(user2.is_none());
}

Error Case Mocks

#[tokio::test]
async fn test_error_case() {
    let mut mock_repo = MockUserRepository::new();

    // Mock returning error
    mock_repo
        .expect_create_with_txn()
        .returning(|_, _| Err(RepositoryError::DatabaseError("接続エラー".to_string())));

    let service = UserService::new(Arc::new(mock_repo));
    let result = service.create(user_data).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        ServiceError::RepositoryError(_) => (),
        _ => panic!("Unexpected error type"),
    }
}

Running Tests

Commands

# Run all tests
cargo test

# Run specific test
cargo test test_create_user

# Show output
cargo test -- --nocapture

# Disable parallel execution
cargo test -- --test-threads=1

# Run integration tests only
cargo test --test integration_test

Debug

#[tokio::test]
async fn test_with_logging() {
    // Enable logging in test
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .init();

    // Test code
    let result = service.create(data).await;

    // Debug output
    println!("Result: {:?}", result);

    assert!(result.is_ok());
}

Test Patterns

Success Case

#[tokio::test]
async fn test_success_case() {
    // Arrange
    let mock = setup_mock_success();

    // Act
    let result = function_under_test(mock).await;

    // Assert
    assert!(result.is_ok());
}

Failure Case

#[tokio::test]
async fn test_failure_case() {
    // Arrange
    let mock = setup_mock_failure();

    // Act
    let result = function_under_test(mock).await;

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

Boundary Value Testing

#[tokio::test]
async fn test_boundary_values() {
    // Empty string
    assert!(validate("").is_err());

    // Minimum length
    assert!(validate("a").is_ok());

    // Maximum length
    assert!(validate(&"a".repeat(255)).is_ok());

    // Maximum length + 1
    assert!(validate(&"a".repeat(256)).is_err());
}

Parameterized Tests

#[tokio::test]
async fn test_validation() {
    let test_cases = vec![
        ("", false),
        ("a", true),
        ("valid@example.com", true),
        ("invalid", false),
    ];

    for (input, expected) in test_cases {
        let result = validate_email(input);
        assert_eq!(result.is_ok(), expected, "Failed for input: {}", input);
    }
}

Best Practices

Test Names

// ✅ Good: Clear what is tested
#[tokio::test]
async fn test_create_user_with_valid_data_returns_user() { }

#[tokio::test]
async fn test_create_user_with_duplicate_email_returns_error() { }

// ❌ Bad: Vague
#[tokio::test]
async fn test1() { }

#[tokio::test]
async fn test_user() { }

Test Independence

// ✅ Create independent mocks per test
#[tokio::test]
async fn test_a() {
    let mock = MockRepo::new();
    // Test A
}

#[tokio::test]
async fn test_b() {
    let mock = MockRepo::new();
    // Test B (independent from test_a)
}

Assertion Messages

// ✅ Provide details on failure
assert_eq!(
    result.len(),
    expected_len,
    "User count mismatch. Expected: {}, Actual: {}",
    expected_len,
    result.len()
);

Common Patterns

Setup Helpers

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

    fn setup_mock_repository() -> MockUserRepository {
        let mut mock = MockUserRepository::new();
        mock.expect_create_with_txn()
            .returning(|_, data| Ok(create_test_user(data)));
        mock
    }

    fn create_test_user(data: UserData) -> User {
        User {
            id: 1,
            name: data.name,
            email: data.email,
            created_at: Utc::now(),
        }
    }

    #[tokio::test]
    async fn test_example() {
        let mock = setup_mock_repository();
        // Test code
    }
}

Async Tests

#[tokio::test]
async fn test_async_function() {
    let result = async_function().await;
    assert!(result.is_ok());
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_concurrent_operations() {
    // Concurrent test
}