| name | Unit Test Writing |
| description | This skill should be used when the user asks to "write tests", "add unit tests", "create test cases", "test this function", "add test coverage", or discusses testing strategies and test implementation. |
Unit Test Writing Guidelines
When writing unit tests, follow these core principles and guidelines.
Core Principles
1. No Duplicate Testing
Each test should verify a unique behavior. Avoid redundant test cases that exercise the same code path.
Bad: Testing a function that processes a list with separate tests for 1 element, 2 elements, and n elements when they all follow the same logic.
Good: Test the meaningful edge cases only - empty list, single element (if there's special handling), and a representative case for multiple elements.
2. Mock External Packages Only
Use mocks for external dependencies from outside the module. Do NOT mock:
- Core/standard library modules (typing, collections, etc.)
- Data classes and models (pydantic, dataclasses, attrs, TypedDict)
- Pure utility functions within the same package
Mock these:
- External API clients (requests, httpx, boto3)
- Database connections and ORMs
- File system operations (when testing logic, not I/O)
- Third-party service integrations
- Time/datetime for deterministic tests
3. Never Mock Private Methods
Private methods (_method in Python, #method in JS) should be exercised through their public interface, not mocked. If you need to mock a private method, it's a sign the code should be refactored.
Test File Location
Place test files alongside source files:
src/
user_service.py
test_user_service.py
utils/
helpers.py
test_helpers.py
Language-Specific Guidelines
Python (pytest)
Use pytest unless the project already uses unittest.
import pytest
from unittest.mock import Mock, patch, MagicMock
# Fixture for reusable setup
@pytest.fixture
def user_service(mock_db):
return UserService(db=mock_db)
# Mock external dependency
@pytest.fixture
def mock_db():
return Mock(spec=DatabaseClient)
# Test naming: test_<method>_<scenario>_<expected>
def test_create_user_valid_input_returns_user(user_service, mock_db):
# Arrange
mock_db.insert.return_value = {"id": 1, "name": "Alice"}
# Act
result = user_service.create_user("Alice")
# Assert
assert result.name == "Alice"
mock_db.insert.assert_called_once()
# Use parametrize for varying inputs with same logic
@pytest.mark.parametrize("invalid_name", ["", None, "x" * 256])
def test_create_user_invalid_name_raises(user_service, invalid_name):
with pytest.raises(ValidationError):
user_service.create_user(invalid_name)
Do NOT mock:
- Pydantic models - instantiate them directly
- Dataclasses - use real instances
- Pure functions in the same module
JavaScript/TypeScript (Jest/Vitest)
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserService } from './user-service';
// Mock external module
vi.mock('./api-client', () => ({
ApiClient: vi.fn().mockImplementation(() => ({
post: vi.fn(),
})),
}));
describe('UserService', () => {
let service: UserService;
let mockApiClient: MockedObject<ApiClient>;
beforeEach(() => {
mockApiClient = new ApiClient() as MockedObject<ApiClient>;
service = new UserService(mockApiClient);
});
it('creates user with valid input', async () => {
mockApiClient.post.mockResolvedValue({ id: 1, name: 'Alice' });
const result = await service.createUser('Alice');
expect(result.name).toBe('Alice');
expect(mockApiClient.post).toHaveBeenCalledOnce();
});
});
Go (testing + testify)
package user
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// Mock for external dependency
type MockDB struct {
mock.Mock
}
func (m *MockDB) Insert(data map[string]any) (map[string]any, error) {
args := m.Called(data)
return args.Get(0).(map[string]any), args.Error(1)
}
func TestCreateUser_ValidInput_ReturnsUser(t *testing.T) {
// Arrange
mockDB := new(MockDB)
mockDB.On("Insert", mock.Anything).Return(map[string]any{"id": 1, "name": "Alice"}, nil)
service := NewUserService(mockDB)
// Act
result, err := service.CreateUser("Alice")
// Assert
assert.NoError(t, err)
assert.Equal(t, "Alice", result.Name)
mockDB.AssertExpectations(t)
}
Test Structure (AAA Pattern)
Always organize tests with clear sections:
- Arrange - Set up test data and mocks
- Act - Execute the code under test
- Assert - Verify the results
Anti-Patterns to Avoid
- Testing implementation details - Test behavior, not how it's achieved
- Excessive mocking - If everything is mocked, you're not testing real behavior
- One assertion per test dogma - Multiple related assertions in one test is fine
- Testing private methods directly - Always go through public API
- Duplicating tests for trivial variations - Use parametrized tests for input variations
- Mocking what you own - Prefer real implementations of your own simple classes