## Паттерны тестирования Go: моки и интерфейсы **Контекст:** Проект имел 0% тестового покрытия. Для production-сервиса это критично. После внедрения [[atomic-dependency-inversion-go]] стало возможным тестировать сервисы с моками. **Суть:** Используем table-driven тесты с интерфейсными моками. ```go type mockPlaceRepo struct { places []*models.Place } func (m *mockPlaceRepo) GetByID(ctx context.Context, id string) (*models.Place, error) { for _, p := range m.places { if p.ID == id { return p, nil } } return nil, nil } func TestPlaceService_Update_NotOwner(t *testing.T) { repo := &mockPlaceRepo{} svc := NewPlaceService(repo) repo.places = append(repo.places, &models.Place{ ID: "place-1", OwnerID: "owner-1", }) _, err := svc.Update(ctx, UpdatePlaceInput{ ID: "place-1", OwnerID: "owner-2", }, false) if !errors.Is(err, ErrNotYourPlace) { t.Errorf("expected ErrNotYourPlace, got %v", err) } } ``` **Table-driven test pattern:** ```go func TestAuthService_Login(t *testing.T) { tests := []struct { name string email string password string wantErr error }{ {"valid credentials", "user@test.com", "pass", nil}, {"wrong password", "user@test.com", "wrong", ErrInvalidCreds}, {"banned user", "banned@test.com", "pass", ErrUserBanned}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, _, err := svc.Login(ctx, LoginInput{tt.email, tt.password}) if !errors.Is(err, tt.wantErr) { t.Error(...) } }) } } ``` **Базовая структура тестов для нового сервиса:** 1. Создать mock репозитория 2. Инициализировать сервис с моком 3. Вызвать метод 4. Проверить результат и ошибку **Связанные заметки:** [[decision-test-strategy]], [[atomic-dependency-inversion-go]] **Источник:** Реализация тестов `services/auth_test.go`, `services/places_test.go` #testing #golang #best-practice #clean-architecture