# test-writer > Guides test creation for Polibase following strict testing standards. Activates when writing tests or creating test files. Enforces external service mocking (no real API calls), async/await patterns, test independence, and proper use of pytest-asyncio to prevent CI failures and API costs. - Author: okodoon - Repository: trust-chain-organization/sagebase - Version: 20260207164830 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/trust-chain-organization/sagebase - Web: https://mule.run/skillshub/@@trust-chain-organization/sagebase~test-writer:20260207164830 --- --- name: test-writer description: Guides test creation for Polibase following strict testing standards. Activates when writing tests or creating test files. Enforces external service mocking (no real API calls), async/await patterns, test independence, and proper use of pytest-asyncio to prevent CI failures and API costs. --- # Test Writer ## Purpose Guide test creation following Polibase testing standards with proper mocking, async/await patterns, and independence from external services. ## When to Activate This skill activates automatically when: - Writing new tests - Creating test files in `tests/` directory - User mentions "test", "pytest", or "testing" - Reviewing existing test code ## โšก TDD Workflow (Test-First Development) **ALWAYS write tests BEFORE implementation!** ### Red-Green-Refactor Cycle 1. **๐Ÿ”ด Red**: Write a failing test ```python # Write test first - it will fail (no implementation yet) @pytest.mark.asyncio async def test_create_politician_saves_to_repository(): mock_repo = AsyncMock(spec=IPoliticianRepository) mock_repo.create.return_value = Politician(id=1, name="ๅฑฑ็”ฐๅคช้ƒŽ") usecase = CreatePoliticianUseCase(mock_repo) result = await usecase.execute(CreatePoliticianInputDTO(name="ๅฑฑ็”ฐๅคช้ƒŽ")) mock_repo.create.assert_awaited_once() ``` 2. **๐ŸŸข Green**: Write minimal code to pass ```python # Now implement just enough to make test pass class CreatePoliticianUseCase: async def execute(self, input_dto): politician = Politician(name=input_dto.name) await self.repository.create(politician) ``` 3. **โ™ป๏ธ Refactor**: Improve code while keeping tests green ```python # Refactor with confidence - tests verify behavior class CreatePoliticianUseCase: async def execute(self, input_dto): # Add validation if not input_dto.name: raise ValueError("Name required") # Extract to method politician = self._create_entity(input_dto) return await self.repository.create(politician) ``` ### TDD Benefits - โœ… Forces you to think about API design before implementation - โœ… Tests serve as documentation - โœ… Refactoring is safe (tests catch regressions) - โœ… Code is naturally testable (designed for testing) **Remember**: If you write implementation first, you're not doing TDD! ## ๐Ÿšซ CRITICAL: Never Call External Services **ABSOLUTELY FORBIDDEN in tests:** - โŒ Real API calls to Google Gemini or any LLM - โŒ Actual HTTP requests to external websites - โŒ Real database connections (except integration tests) - โŒ File system operations outside temp directories - โŒ Network connections of any kind **Why?** - Tests must run in CI/CD without API keys - Tests must be fast (< 1 second per test) - Tests must be deterministic (same result every time) - Tests must not incur API costs ## Quick Checklist Before committing tests: - [ ] **No External Calls**: All external services mocked - [ ] **Fast Execution**: Each test runs in < 1 second - [ ] **Isolated**: Tests don't depend on each other - [ ] **Deterministic**: Same result every time - [ ] **Clear Names**: Test name describes what it tests - [ ] **Arrange-Act-Assert**: Clear test structure - [ ] **Async Properly**: Uses `@pytest.mark.asyncio` and `AsyncMock` - [ ] **Mock Verification**: Asserts mock calls when relevant - [ ] **Type Hints**: Complete type annotations - [ ] **Nullable Fields**: `T | None` ใƒ•ใ‚ฃใƒผใƒซใƒ‰ใฏ `None` ใ‚ฑใƒผใ‚นใ‚‚ใƒ†ใ‚นใƒˆ - [ ] **List Results**: ใƒชใ‚นใƒˆ่ฟ”ๅดใƒกใ‚ฝใƒƒใƒ‰ใฏ 0ไปถใƒป1ไปถใƒป่ค‡ๆ•ฐไปถ ใ‚’ใƒ†ใ‚นใƒˆ ## Test Structure ``` tests/ โ”œโ”€โ”€ unit/ # Fast, isolated tests โ”‚ โ”œโ”€โ”€ domain/ # Domain entities and services โ”‚ โ”œโ”€โ”€ application/ # Use cases (with mocks) โ”‚ โ””โ”€โ”€ infrastructure/ # External services (with mocks) โ”œโ”€โ”€ integration/ # Tests with real database โ”œโ”€โ”€ evaluation/ # LLM evaluation (manual only, not in CI) โ””โ”€โ”€ conftest.py # Shared fixtures ``` ## Core Testing Patterns ### 1. Mocking External Services **Always use `AsyncMock` with `spec=` parameter:** ```python from unittest.mock import AsyncMock @pytest.fixture def mock_llm_service(): # ALWAYS use spec= to catch typos and wrong method calls mock = AsyncMock(spec=ILLMService) mock.generate_text.return_value = "Mocked response" return mock ``` **โš ๏ธ Why `spec=` is CRITICAL:** ```python # โŒ WITHOUT spec= - typos go undetected mock = AsyncMock() await mock.genrate_text("prompt") # Typo! Test still passes! # โœ… WITH spec= - typos caught immediately mock = AsyncMock(spec=ILLMService) await mock.genrate_text("prompt") # AttributeError! ``` **Use `AsyncMock` for async methods, never `MagicMock`:** ```python # โŒ WRONG - MagicMock for async function mock_repo = MagicMock(spec=IPoliticianRepository) result = await mock_repo.create(politician) # Error! # โœ… CORRECT - AsyncMock for async function mock_repo = AsyncMock(spec=IPoliticianRepository) result = await mock_repo.create(politician) # Works! ``` ### 2. Async Tests **Use pytest-asyncio:** ```python @pytest.mark.asyncio async def test_async_function(mock_repo): result = await usecase.execute(input_dto) assert result.success ``` ### 3. Test Independence **Each test is self-contained:** ```python def test_create_politician(mock_repo): # Setup mock mock_repo.save.return_value = Politician(id=1, name="Test") # Execute result = usecase.execute(input_dto) # Assert assert result.success ``` ## Templates Use templates in `templates/` directory for: - Domain service tests - Use case tests with mocks - Repository integration tests - External service tests with mocks ## Detailed Reference For comprehensive testing patterns, mocking strategies, and best practices, see [reference.md](reference.md). ## Examples See [examples.md](examples.md) for concrete test examples at each layer. ## Running Tests ```bash # Run all tests docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest # Run specific test file docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest tests/unit/domain/test_speaker_domain_service.py # Run with coverage docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest --cov=src # Run only unit tests docker compose -f docker/docker-compose.yml [-f docker/docker-compose.override.yml] exec sagebase uv run pytest tests/unit/ ``` ## Common Anti-Patterns 1. **โŒ Real API Calls**: Most common mistake! 2. **โŒ Testing Implementation Details**: Test public interfaces 3. **โŒ Test Dependencies**: Each test must be independent 4. **โŒ Missing Async/Await**: Forget `@pytest.mark.asyncio` 5. **โŒ No Mock Verification**: Don't check if mocks were called 6. **โŒ `return` in `patch` fixture**: `with patch(...)` ๅ†…ใง `return` ใ™ใ‚‹ใจpatchใ‚นใ‚ณใƒผใƒ—ใŒๅˆ‡ใ‚Œใ‚‹ โ†’ `yield` ใ‚’ไฝฟใ† 7. **โŒ ๅ†…้ƒจใƒกใ‚ฝใƒƒใƒ‰ใฎใƒขใƒƒใ‚ฏไธŠๆ›ธใ**: `presenter._run_async = MagicMock(...)` ใฏใƒ—ใƒญใƒ€ใ‚ฏใ‚ทใƒงใƒณใ‚ณใƒผใƒ‰ใฎๆคœ่จผใ‚’ใƒใ‚คใƒ‘ใ‚นใ™ใ‚‹ See [reference.md](reference.md) for detailed explanations and fixes.