> Писали ли интеграционные тесты или моки для сервисов с Dependency Injection (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Да, писал. Для сервисов с Dependency Injection в Go использую подход с интерфейсами и моками на основе библиотек testify/mock или gomock. Интеграционные тесты покрывают реальные зависимости (база данных, внешние API) через testcontainers или docker-compose. Основной принцип - юнит-тесты с моками для изоляции логики, интеграционные - для проверки взаимодействия компонентов. В production-коде всегда разделяю интерфейсы и реализации, чтобы тестируемость была заложена архитектурно.
Подробное объяснение
В Go Dependency Injection обычно реализуется через конструкторы, принимающие интерфейсы. Это даёт два уровня тестирования:
-
Юнит-тесты с моками - когда мы подменяем зависимости (репозитории, клиенты внешних сервисов) на mock-объекты. Проверяем только логику сервиса: валидацию, обработку ошибок, порядок вызовов.
-
Интеграционные тесты - когда поднимаем реальные зависимости (PostgreSQL, Redis, Kafka) и проверяем, что сервис корректно работает с ними. Использую testcontainers-go для изоляции окружения.
Ключевой момент - интерфейсы должны быть маленькими и отражать потребности потребителя, а не реализации. Это позволяет легко создавать моки и не переписывать тесты при изменении деталей реализации.
Для моков предпочитаю gomock (генерирует код) или testify/mock (ручное написание). Второй удобнее для быстрых сценариев, первый - для больших проектов с множеством интерфейсов.
На практике
В реальных проектах применяю следующую схему:
- Сервисный слой - юнит-тесты с моками на 100% покрытие бизнес-логики.
- Репозитории - интеграционные тесты с реальной БД (testcontainers), проверяю SQL-запросы и транзакции.
- HTTP-хендлеры - тесты с mock-сервисами, проверяю статусы, JSON-ответы, валидацию.
- Внешние интеграции - contract tests или тесты с wiremock/testcontainers для эмуляции внешних API.
Для DI-контейнера (fx, wire, dig) не пишу отдельные тесты - проверяю только, что граф зависимостей собирается без ошибок в тестовом окружении.
Пример кода
GO// Интерфейс для DItype 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)}// Интеграционный тест с testcontainersfunc 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, забывая про ошибки и граничные случаи.
- Слишком большие интерфейсы - моки становятся громоздкими, тесты теряют читаемость.
> Похожие задачи по backend
В чем разница наследования в Go и классического наследования
Что такое WebSocket и как он работает
Что лучше использовать в Go: int8, int32, int64 или просто int
Как запустить профилировщик в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью