← Назад к вопросам

Как мокать зависимости в Go?

2.3 Middle🔥 151 комментариев
#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы мокирования зависимостей в Go

В Go существует несколько подходов для мокирования зависимостей в тестах, каждый из которых подходит для разных ситуаций. Основные методы включают использование интерфейсов, внедрение зависимостей, библиотеки для мокирования и ручное создание заглушек.

Использование интерфейсов и внедрение зависимостей

Это наиболее классический и рекомендуемый подход в Go. Он основан на принципе Dependency Injection (DI) и предполагает, что ваш код зависит от интерфейсов, а не от конкретных реализаций.

Пример структуры с интерфейсом

// Интерфейс для зависимости
type DataStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

// Сервис, использующий интерфейс
type UserService struct {
    store DataStore
}

func NewUserService(store DataStore) *UserService {
    return &UserService{store: store}
}

func (s *UserService) ProcessUser(id string) error {
    user, err := s.store.GetUser(id)
    if err != nil {
        return err
    }
    // Логика обработки
    return s.store.SaveUser(user)
}

Мок для тестирования

// Моковая реализация DataStore
type MockDataStore struct {
    GetUserFunc func(id string) (*User, error)
    SaveUserFunc func(user *User) error
}

func (m *MockDataStore) GetUser(id string) (*User, error) {
    if m.GetUserFunc != nil {
        return m.GetUserFunc(id)
    }
    return nil, nil
}

func (m *MockDataStore) SaveUser(user *User) error {
    if m.SaveUserFunc != nil {
        return m.SaveUserFunc(user)
    }
    return nil
}

// Использование мока в тесте
func TestUserService_ProcessUser(t *testing.T) {
    mockStore := &MockDataStore{
        GetUserFunc: func(id string) (*User, error) {
            return &User{ID: id, Name: "Test"}, nil
        },
        SaveUserFunc: func(user *User) error {
            // Проверяем, что сохранение вызвано с правильными данными
            if user.Name != "Test" {
                return errors.New("invalid user")
            }
            return nil
        },
    }
    
    service := NewUserService(mockStore)
    err := service.ProcessUser("123")
    assert.NoError(t, err)
}

Библиотеки для автоматического мокирования

Для более сложных случаев можно использовать специализированные библиотеки, которые генерируют моки автоматически.

github.com/golang/mock/gomock

Одна из самых популярных библиотек. Она использует кодогенерацию для создания моков на основе интерфейсов.

// Генерация мока (через mockgen)
// mockgen -source=datastore.go -destination=mock_datastore.go

// Использование в тесте
func TestWithGoMock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    
    mockStore := mock.NewMockDataStore(ctrl)
    // Ожидаем конкретный вызов
    mockStore.EXPECT().
        GetUser(gomock.Eq("123")).
        Return(&User{ID: "123"}, nil)
    
    mockStore.EXPECT().
        SaveUser(gomock.Any()).
        Return(nil)
    
    service := NewUserService(mockStore)
    err := service.ProcessUser("123")
    assert.NoError(t, err)
}

github.com/stretchr/testify/mock

Более простой библиотека, которая не требует кодогенерации.

func TestWithTestifyMock(t *testing.T) {
    mockStore := &MockDataStore{}
    mockStore.On("GetUser", "123").Return(&User{ID: "123"}, nil)
    mockStore.On("SaveUser", mock.AnythingOfType("*User")).Return(nil)
    
    service := NewUserService(mockStore)
    err := service.ProcessUser("123")
    assert.NoError(t, err)
    
    mockStore.AssertExpectations(t) // Проверяем все ожидания
}

Мокирование внешних HTTP-сервисов

Для мокирования HTTP-зависимостей часто используют httptest из стандартной библиотеки.

func TestExternalAPICall(t *testing.T) {
    // Создание тестового сервера
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"id": "123"}`))
    })
    
    testServer := httptest.NewServer(handler)
    defer testServer.Close()
    
    // Клиент использует тестовый сервер
    client := &APIClient{BaseURL: testServer.URL}
    data, err := client.GetData("123")
    assert.NoError(t, err)
    assert.Equal(t, "123", data.ID)
}

Мокирование времени и других сложных зависимостей

Для мокирования времени можно использовать интерфейсы:

type TimeProvider interface {
    Now() time.Time
}

type RealTime struct{}
func (r RealTime) Now() time.Time { return time.Now() }

type MockTime struct {
    FixedTime time.Time
}
func (m MockTime) Now() time.Time { return m.FixedTime }

// В коде используем TimeProvider вместо direct time.Now()

Практические рекомендации

  • Принцип наименьших усилий: начинайте с простых ручных моков на основе интерфейсов.
  • Избегайте мокирования всего: мокируйте только внешние зависимости (базы данных, API, файловые системы), но не внутреннюю логику.
  • Используйте кодогенерацию для сложных интерфейсов: gomock полезен когда интерфейсы имеют много методов.
  • Тестируйте поведение, не реализацию: убедитесь что тесты проверяют бизнес-логику, а не просто вызовы методов.
  • Встраивайте моки в структуры: для частичного мокирования можно использовать встраивание реальной реализации.
  • Мокирование должно быть простым: сложные моки с большим количеством ожиданий часто указывают на проблему в дизайне кода.

Выбор стратегии

СитуацияПодход
Простой интерфейс с 1-3 методамиРучной мок
Комплексный интерфейс, много методовgomock или testify/mock
HTTP-зависимостиhttptest.Server
Глобальные зависимости (time, rand)Интерфейсы + DI
Интеграционные тестыФейковые реализации (в памяти)

Правильное мокирование зависимостей в Go напрямую связано с качественным дизайном кода — использование интерфейсов и внедрения зависимостей не только облегчает тестирование, но и улучшает архитектуру приложения в целом.

Как мокать зависимости в Go? | PrepBro