Status: Established Date: November 22, 2025 Related Issues: #303 (CONV-MCP-STANDUP)
Testing pattern for MCP Skills (reusable workflow components that handle specific tasks with minimal token usage).
All skill tests use a skill fixture that mocks external dependencies:
@pytest.fixture
def skill():
"""Create skill instance with mocked dependencies"""
with (
patch("services.integrations.mcp.skills.standup_workflow_skill.MorningStandupWorkflow"),
patch("services.integrations.mcp.skills.standup_workflow_skill.StandupOrchestrationService"),
patch("services.integrations.mcp.skills.standup_workflow_skill.GitHubDomainService"),
patch("services.integrations.mcp.skills.standup_workflow_skill.SlackDomainService"),
patch("services.integrations.mcp.skills.standup_workflow_skill.UserPreferenceManager"),
patch("services.integrations.mcp.skills.standup_workflow_skill.SessionPersistenceManager"),
):
return StandupWorkflowSkill()
Key Points:
Tests are organized into semantic classes covering major functionality areas:
@pytest.mark.asyncio
async def test_execute_success(self, skill, sample_standup):
"""Test successful execution"""
skill.workflow = AsyncMock()
skill.workflow.generate_standup = AsyncMock(return_value=sample_standup)
result = await skill.execute({"user_id": "test"})
assert result["success"] is True
skill.workflow.generate_standup.assert_called_once()
Use AsyncMock for async method mocking, @pytest.mark.asyncio decorator for async test functions.
@pytest.mark.asyncio
async def test_partial_failure_continues(self, skill, sample_standup):
"""One system failure shouldn't stop entire workflow"""
skill.workflow = AsyncMock()
skill._post_to_slack = AsyncMock(side_effect=Exception("API error"))
skill._process_github = AsyncMock(return_value={"success": True})
result = await skill.execute({"user_id": "test", "include_slack": True, "include_github": True})
assert result["success"] is True # Overall success despite Slack failure
assert "github" in result["posted_to"]
assert "slack" not in result["posted_to"]
Test graceful degradation where partial failures don’t crash the entire skill.
def test_format_multiple_formats(self, skill, sample_standup):
"""Support multiple output formats"""
markdown = skill._format_standup(sample_standup, format_type="markdown")
plain = skill._format_standup(sample_standup, format_type="plain")
json_out = skill._format_standup(sample_standup, format_type="json")
assert markdown["format"] == "markdown"
assert plain["format"] == "plain"
assert json_out == sample_standup # JSON returns raw data
Test multiple output format paths independently.
Use a sample_standup fixture with realistic data structure:
@pytest.fixture
def sample_standup():
"""Sample standup data"""
return {
"user_id": str(uuid4()),
"generated_at": datetime.now().isoformat(),
"generation_time_ms": 1500,
"yesterday_accomplishments": [...],
"today_priorities": [...],
"blockers": [...],
"github_activity": {...},
"time_saved_minutes": 18,
}
See: tests/unit/integrations/mcp/test_standup_workflow_skill.py (22 tests)
pattern-mcp-skill-base.md