> Писали ли интеграционные тесты или моки для сервисов с Dependency Injection (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Wildberries

Стек: Go

> Пример ответа

Короткий ответ

Да, писал. Для сервисов с Dependency Injection в Go использую подход с интерфейсами и моками на основе библиотек testify/mock или gomock. Интеграционные тесты покрывают реальные зависимости (база данных, внешние API) через testcontainers или docker-compose. Основной принцип - юнит-тесты с моками для изоляции логики, интеграционные - для проверки взаимодействия компонентов. В production-коде всегда разделяю интерфейсы и реализации, чтобы тестируемость была заложена архитектурно.

Подробное объяснение

В Go Dependency Injection обычно реализуется через конструкторы, принимающие интерфейсы. Это даёт два уровня тестирования:

  1. Юнит-тесты с моками - когда мы подменяем зависимости (репозитории, клиенты внешних сервисов) на mock-объекты. Проверяем только логику сервиса: валидацию, обработку ошибок, порядок вызовов.

  2. Интеграционные тесты - когда поднимаем реальные зависимости (PostgreSQL, Redis, Kafka) и проверяем, что сервис корректно работает с ними. Использую testcontainers-go для изоляции окружения.

Ключевой момент - интерфейсы должны быть маленькими и отражать потребности потребителя, а не реализации. Это позволяет легко создавать моки и не переписывать тесты при изменении деталей реализации.

Для моков предпочитаю gomock (генерирует код) или testify/mock (ручное написание). Второй удобнее для быстрых сценариев, первый - для больших проектов с множеством интерфейсов.

На практике

В реальных проектах применяю следующую схему:

  • Сервисный слой - юнит-тесты с моками на 100% покрытие бизнес-логики.
  • Репозитории - интеграционные тесты с реальной БД (testcontainers), проверяю SQL-запросы и транзакции.
  • HTTP-хендлеры - тесты с mock-сервисами, проверяю статусы, JSON-ответы, валидацию.
  • Внешние интеграции - contract tests или тесты с wiremock/testcontainers для эмуляции внешних API.

Для DI-контейнера (fx, wire, dig) не пишу отдельные тесты - проверяю только, что граф зависимостей собирается без ошибок в тестовом окружении.

Пример кода

GO
// Интерфейс для DI
type UserRepository interface {
GetByID(ctx context.Context, id int) (*User, error)
Save(ctx context.Context, u *User) error
}
// Сервис с инъекцией зависимости
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
// Юнит-тест с моком
type mockUserRepo struct {
mock.Mock
}
func (m *mockUserRepo) GetByID(ctx context.Context, id int) (*User, error) {
args := m.Called(ctx, id)
return args.Get(0).(*User), args.Error(1)
}
func TestUserService_GetByID(t *testing.T) {
repo := new(mockUserRepo)
repo.On("GetByID", mock.Anything, 42).Return(&User{ID: 42}, nil)
svc := NewUserService(repo)
user, err := svc.GetByID(context.Background(), 42)
assert.NoError(t, err)
assert.Equal(t, 42, user.ID)
repo.AssertExpectations(t)
}
// Интеграционный тест с testcontainers
func TestUserRepository_Integration(t *testing.T) {
ctx := context.Background()
pg, _ := postgres.Run(ctx, "postgres:16",
postgres.WithDatabase("testdb"),
postgres.WithUsername("user"),
postgres.WithPassword("pass"))
defer pg.Terminate(ctx)
db, _ := sql.Open("postgres", pg.ConnectionString())
repo := NewPostgresUserRepository(db)
// реальные операции с БД
err := repo.Save(ctx, &User{ID: 1, Name: "Alice"})
assert.NoError(t, err)
}

Как отвечать на собеседовании

Начни с конкретного примера из практики: какой сервис тестировал, какие зависимости мокал, какие интеграционные тесты писал. Подчеркни, что DI-архитектура упрощает тестирование, но требует дисциплины в проектировании интерфейсов. Упомяни trade-off между моками и реальными зависимостями: моки быстрее, но не ловят проблемы интеграции. Если спросят про инструменты - назови gomock, testify, testcontainers-go. Покажи понимание, когда какой подход уместен: юнит-тесты для бизнес-логики, интеграционные - для критичных путей с БД или внешними системами.

Что проверяет интервьюер

Интервьюер оценивает:

  • понимание принципов DI и их влияния на тестируемость;
  • умение выбирать правильный уровень тестирования (юнит vs интеграционный);
  • знание конкретных инструментов Go-экосистемы;
  • способность проектировать интерфейсы, удобные для мокирования;
  • опыт с реальными проблемами: flaky-тесты, управление тестовым окружением, скорость выполнения.

Типичные ошибки

  • Мокать всё подряд, включая тривиальные зависимости - тесты становятся хрупкими и не отражают реальность.
  • Писать интеграционные тесты без изоляции окружения - они зависят от локальной БД или внешних сервисов.
  • Игнорировать интерфейсы в пользу конкретных типов - невозможно подменить зависимость.
  • Тестировать только happy path, забывая про ошибки и граничные случаи.
  • Слишком большие интерфейсы - моки становятся громоздкими, тесты теряют читаемость.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью