Some checks are pending
Test Dashboard / test-and-dashboard (push) Waiting to run
Named for Milton Waddams, who was relocated to the basement with boxes of legacy documents. He handles the .doc and .xls files from 1997 that nobody else wants to touch. - Rename package from mcp-office-tools to mcwaddams - Update author to Ryan Malloy - Update all imports and references - Add Office Space themed README narrative - All 53 tests passing
MCP Office Tools Testing Strategy
This document outlines the comprehensive testing strategy for the mixin-based FastMCP Office Tools server.
Testing Architecture Overview
The testing suite is designed around the mixin architecture pattern and follows FastMCP best practices:
Test Organization
tests/
├── conftest.py # Shared fixtures and configuration
├── test_server.py # Integration tests for the composed server
├── test_mixins.py # Mixin architecture and composition tests
├── test_universal_mixin.py # Unit tests for UniversalMixin
├── test_word_mixin.py # Unit tests for WordMixin
├── test_excel_mixin.py # Unit tests for ExcelMixin (future)
├── test_powerpoint_mixin.py # Unit tests for PowerPointMixin (future)
└── README.md # This file
Testing Patterns
1. Mixin Unit Testing
Each mixin is tested independently with comprehensive mocking:
@pytest.mark.asyncio
@patch('mcp_office_tools.utils.validation.resolve_office_file_path')
@patch('mcp_office_tools.utils.validation.validate_office_file')
@patch('mcp_office_tools.utils.file_detection.detect_format')
async def test_extract_text_success(mock_detect, mock_validate, mock_resolve, mixin):
# Setup mocks
mock_resolve.return_value = "/test.csv"
mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = {"category": "data", "extension": ".csv"}
# Mock internal methods
with patch.object(mixin, '_extract_text_by_category') as mock_extract:
mock_extract.return_value = {"text": "test", "method_used": "pandas"}
result = await mixin.extract_text("/test.csv")
assert "text" in result
2. Tool Registration Testing
Verify that mixins register tools correctly:
def test_tool_registration_count(self):
"""Test that all expected tools are registered."""
app = FastMCP("Test Office Tools")
universal = UniversalMixin(app)
assert len(app._tools) == 6 # 6 universal tools
word = WordMixin(app)
assert len(app._tools) == 7 # 6 universal + 1 word tool
3. FastMCP Session Testing
Test tools through FastMCP's testing framework:
@pytest.mark.asyncio
async def test_tool_execution_via_session(self):
"""Test tool execution through FastMCP test session."""
session = create_test_session(app)
result = await session.call_tool("get_supported_formats", {})
assert "supported_extensions" in result
4. Error Handling Testing
Comprehensive error handling with proper exception types:
@pytest.mark.asyncio
async def test_extract_text_nonexistent_file(self, mixin):
"""Test extract_text with nonexistent file raises OfficeFileError."""
with pytest.raises(OfficeFileError):
await mixin.extract_text("/nonexistent/file.docx")
Mocking Strategies
File Operations
Use the MockValidationContext for consistent file operation mocking:
def test_with_mock_validation(mock_validation_context):
with mock_validation_context(
resolve_path="/test.docx",
validation_result={"is_valid": True, "errors": []},
format_detection={"category": "word", "extension": ".docx"}
):
# Test with mocked file operations
pass
Office Document Processing
Mock internal processing methods to test tool logic without file dependencies:
with patch.object(mixin, '_extract_text_by_category') as mock_extract:
mock_extract.return_value = {
"text": "extracted text",
"method_used": "python-docx",
"methods_tried": ["python-docx"]
}
result = await mixin.extract_text(file_path)
Test Categories
Unit Tests (@pytest.mark.unit)
- Individual mixin functionality
- Helper method testing
- Parameter validation
- Error handling
Integration Tests (@pytest.mark.integration)
- Full server composition
- Cross-mixin interactions
- Tool execution via sessions
- End-to-end workflows
Tool Functionality Tests (@pytest.mark.tool_functionality)
- Specific tool behavior
- Parameter handling
- Output validation
- Method selection logic
Running Tests
All Tests
uv run pytest
Specific Test Categories
# Unit tests only
uv run pytest -m unit
# Integration tests only
uv run pytest -m integration
# Tool functionality tests
uv run pytest -m tool_functionality
# Specific mixin tests
uv run pytest tests/test_universal_mixin.py
# With coverage
uv run pytest --cov=mcp_office_tools
Fast Development Cycle
# Skip integration tests for faster feedback
uv run pytest -m "not integration"
Test Fixtures
Shared Fixtures (conftest.py)
fast_mcp_app: Clean FastMCP app instanceuniversal_mixin: UniversalMixin instanceword_mixin: WordMixin instancecomposed_app: Fully composed app with all mixinstest_session: FastMCP test sessiontemp_dir: Temporary directory for test filesmock_csv_file: Temporary CSV file with test datamock_docx_file: Mock DOCX file structure
Mock Data Fixtures
mock_file_validation: Standard validation responsemock_format_detection: Standard format detection responsemock_text_extraction_result: Standard text extraction resultmock_document_metadata: Standard document metadata
Best Practices
1. Fast Test Execution
- Mock all file I/O operations
- Use temporary files only when necessary
- Keep tests under 1 second unless marked as integration
2. Comprehensive Mocking
- Mock external dependencies at the boundary
- Test internal logic without external dependencies
- Use realistic mock data that reflects actual tool behavior
3. Clear Test Intent
- One behavior per test
- Descriptive test names
- Clear arrange/act/assert structure
4. Error Testing
- Test all error conditions
- Verify specific exception types
- Test error messages for helpfulness
5. Tool Functionality Focus
- Test tool behavior, not just registration
- Verify output structure and content
- Test parameter combinations and edge cases
Advanced Testing Patterns
Testing Async Tool Methods Directly
@pytest.mark.asyncio
async def test_tool_method_directly(universal_mixin):
"""Test tool method directly without session overhead."""
# Direct method testing for unit-level validation
with patch('mcp_office_tools.utils.validation.validate_office_file'):
result = await universal_mixin.extract_text("/test.csv")
assert result is not None
Testing Tool Parameter Validation
@pytest.mark.asyncio
async def test_parameter_validation(mixin):
"""Test tool parameter validation and handling."""
# Test various parameter combinations
result = await mixin.extract_text(
file_path="/test.csv",
preserve_formatting=True,
include_metadata=False,
method="primary"
)
# Verify parameters were used correctly
assert result["metadata"]["extraction_method"] != "auto"
Testing Mixin Composition
def test_mixin_composition(self):
"""Test that mixin composition works correctly."""
app = FastMCP("Test")
# Initialize mixins in order
universal = UniversalMixin(app)
word = WordMixin(app)
# Verify no tool conflicts
tool_names = set(app._tools.keys())
assert len(tool_names) == 7 # 6 + 1, no duplicates
Future Enhancements
- Property-based testing for document processing
- Performance benchmarking tests
- Memory usage validation
- Stress testing with large documents
- Network operation testing for URL processing
- Security testing for malicious document handling
This testing strategy ensures comprehensive coverage of the mixin-based architecture while maintaining fast test execution and clear test organization.