- Use app.run_stdio_async() instead of deprecated stdio_server import - Aligns with FastMCP 2.11.3 API - Server now starts correctly with uv run mcp-office-tools - Maintains all MCPMixin functionality and tool registration
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.