Files
gh-emillindfors-claude-mark…/commands/rust-test-add-unit.md
2025-11-29 18:25:58 +08:00

7.1 KiB

description
description
Add comprehensive unit tests for a function or module

You are helping add unit tests to Rust code following best practices.

Your Task

Generate comprehensive unit tests for the specified function or module, covering success cases, error cases, and edge cases.

Steps

  1. Identify Target

    Ask the user (if not specified):

    • What function/struct/module to test?
    • Where is it located?

    Or scan the current file for testable functions.

  2. Analyze Function

    Read the function to understand:

    • What it does
    • What inputs it takes
    • What it returns (including error types)
    • What edge cases exist
  3. Create Test Module

    Add or update the #[cfg(test)] module at the bottom of the file:

    #[cfg(test)]
    mod tests {
        use super::*;
    
        // Tests go here
    }
    
  4. Generate Test Cases

    For each function, create tests for:

    Success Cases:

    #[test]
    fn test_[function]_with_valid_input() {
        // Arrange
        let input = create_valid_input();
    
        // Act
        let result = function(input);
    
        // Assert
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), expected_value);
    }
    

    Error Cases:

    #[test]
    fn test_[function]_returns_error_on_invalid_input() {
        let result = function(invalid_input);
    
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), ErrorType::Specific));
    }
    

    Edge Cases:

    #[test]
    fn test_[function]_with_empty_input() {
        let result = function("");
        assert!(result.is_err());
    }
    
    #[test]
    fn test_[function]_with_max_length_input() {
        let long_input = "x".repeat(MAX_LENGTH);
        let result = function(&long_input);
        assert!(result.is_ok());
    }
    
  5. Add Test Fixtures

    Create helper functions for common test data:

    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn create_test_user() -> User {
            User {
                id: "test-id".to_string(),
                email: "test@example.com".to_string(),
            }
        }
    
        fn create_test_user_with_id(id: &str) -> User {
            User {
                id: id.to_string(),
                email: "test@example.com".to_string(),
            }
        }
    
        #[test]
        fn test_with_fixture() {
            let user = create_test_user();
            let result = validate_user(&user);
            assert!(result.is_ok());
        }
    }
    
  6. Add Async Tests if Needed

    For async functions:

    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[tokio::test]
        async fn test_async_function() {
            let result = async_function().await;
            assert!(result.is_ok());
        }
    
        #[tokio::test]
        async fn test_async_error_case() {
            let result = async_function_with_error().await;
            assert!(result.is_err());
        }
    }
    
  7. Add Mock Implementations

    For functions using traits:

    #[cfg(test)]
    mod tests {
        use super::*;
        use std::collections::HashMap;
    
        struct MockRepository {
            data: HashMap<String, User>,
        }
    
        impl MockRepository {
            fn new() -> Self {
                Self { data: HashMap::new() }
            }
    
            fn with_user(mut self, user: User) -> Self {
                self.data.insert(user.id.clone(), user);
                self
            }
        }
    
        #[async_trait]
        impl UserRepository for MockRepository {
            async fn find_by_id(&self, id: &str) -> Result<User, Error> {
                self.data.get(id)
                    .cloned()
                    .ok_or(Error::NotFound(id.to_string()))
            }
        }
    
        #[tokio::test]
        async fn test_service_with_mock() {
            let mock = MockRepository::new()
                .with_user(create_test_user());
    
            let service = UserService::new(mock);
            let user = service.get_user("test-id").await.unwrap();
    
            assert_eq!(user.email, "test@example.com");
        }
    }
    
  8. Add Table-Driven Tests

    For multiple similar test cases:

    #[test]
    fn test_validation_cases() {
        let test_cases = vec![
            ("", false, "Empty input"),
            ("a", true, "Single char"),
            ("abc", true, "Valid input"),
            ("x".repeat(1000).as_str(), false, "Too long"),
        ];
    
        for (input, should_pass, description) in test_cases {
            let result = validate(input);
            assert_eq!(
                result.is_ok(),
                should_pass,
                "Failed for case: {}",
                description
            );
        }
    }
    
  9. Provide Test Summary

    After generating tests:

    ✅ Unit tests added successfully!
    
    ## Tests Created:
    
    ### Success Cases (3):
    - test_create_user_with_valid_email
    - test_update_user_success
    - test_delete_user_success
    
    ### Error Cases (4):
    - test_create_user_with_empty_email
    - test_create_user_with_invalid_email
    - test_update_nonexistent_user
    - test_delete_nonexistent_user
    
    ### Edge Cases (2):
    - test_email_max_length
    - test_special_characters_in_email
    
    ## Test Fixtures:
    - create_test_user()
    - create_test_user_with_email(email)
    
    ## Mock Implementations:
    - MockUserRepository
    
    ## Run Tests:
    ```bash
    cargo test
    cargo test --package [package_name]
    cargo test test_create_user -- --nocapture
    

    Next Steps:

    1. Review generated tests
    2. Add more edge cases if needed
    3. Run tests and verify they pass
    4. Check coverage with cargo tarpaulin
    
    

Test Naming Conventions

Use descriptive names following this pattern:

  • test_[function]_[scenario]_[expected_result]

Examples:

  • test_create_user_with_valid_email_succeeds
  • test_create_user_with_empty_email_returns_validation_error
  • test_parse_config_with_invalid_json_returns_parse_error

Important Patterns

Testing Result Types

#[test]
fn test_returns_ok() {
    let result = function();
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), expected);
}

#[test]
fn test_returns_specific_error() {
    let result = function();
    assert!(result.is_err());

    match result {
        Err(MyError::Specific) => (),  // Expected
        _ => panic!("Wrong error type"),
    }
}

Testing Option Types

#[test]
fn test_returns_some() {
    let result = function();
    assert!(result.is_some());
    assert_eq!(result.unwrap(), expected);
}

#[test]
fn test_returns_none() {
    let result = function();
    assert!(result.is_none());
}

Testing Panics

#[test]
#[should_panic(expected = "expected panic message")]
fn test_panics_on_invalid_input() {
    function_that_panics();
}

Testing with assert_matches

use assert_matches::assert_matches;

#[test]
fn test_error_variant() {
    let result = function();
    assert_matches!(result, Err(Error::Specific { .. }));
}

After Completion

Ask the user:

  1. Do tests pass? (run cargo test)
  2. Are there more scenarios to test?
  3. Should we add integration tests?
  4. Do you want to check test coverage?