TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7293 3.27K
🖥 Монолитные тесты Go API: cлой логики, слой HTTP и трейдоффы

Мы вынесли моки в mock.go и наметили разбиение тестов на слои. Теперь покажем сами тесты двух слоёв и честно разберём, чем платим за такую структуру.

🛠 Бизнес логика (`*_service_test.go`)

Этот файл проверяет только доменную логику. Слой репозитория замокан через mock.go, поэтому тесты гоняются целиком в памяти и работают очень быстро.

Здесь проверяют правила валидации, обработку кривых данных, доменные ошибки и переходы состояний:
func TestAuthServiceLogin(t *testing.T) {
t.Run("login credentials", func(t *testing.T) {
testAuthRepo.GetUsernameOrEmailFunc = func(ctx context.Context, username string) (*models.Users, error) {
hash, _ := bcrypt.GenerateFromPassword([]byte("!Abc1234"), bcrypt.DefaultCost)
return &models.Users{ID: 1, Username: "test_account", Password: string(hash)}, nil
}
req := auth.LoginRequest{Username: "rdev", Password: "!Abc1234"}
_, err := testAuthSvc.Login(context.Background(), req)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
})
}


🛠 HTTP слой (`*_handler_test.go`)

Этот файл проверяет, как API общается с внешним миром. Он поднимает запросы через net/http/httptest и использует моки сервиса, чтобы не дёргать реальную логику. Под проверкой статус коды, сериализация JSON, параметры запроса, заголовки, работа мидлварей вроде rate limiter.

Табличный тест гоняет несколько кейсов входа сразу:
func TestAuthHandlerLogin(t *testing.T) {
tests := []struct {
name string
requestBody any
expectedStatus int
}{
{"complete credentials", loginRequest{"rdev", "!Abc1234"}, http.StatusOK},
{"no password", loginRequest{"rdev", ""}, http.StatusBadRequest},
{"password too short", loginRequest{"rdev", "1234"}, http.StatusBadRequest},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, w := helpers.SetupJSONTestContext(t, http.MethodPost, "/login", tt.requestBody)
testAuthHandler.Login(ctx)
if w.Code != tt.expectedStatus {
t.Errorf("Login() status = %d, want %d", w.Code, tt.expectedStatus)
}
})
}
}


❔ Чем платим

Плюсы:

• баги в роутинге ищутся в тесте хендлера
• баги в расчётах ищутся в тесте сервиса
• изоляция моков снимает циклические импорты
• границы слоёв не дают случайно проверить механику HTTP внутри теста логики

Минусы:

• чуть больше бойлерплейта
• несколько файлов требуют начальной настройки
• при изменении интерфейса сервиса mock.go придётся править руками, если не подключить генератор вроде mockery.

Разделение тестов по ответственности это проверенная для Go стратегия. Изолируете HTTP логику от бизнес логики, выносите моки в один файл и получаете набор тестов, который не разрастается, не ломается циклическими импортами и легко читается.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • ❤ 4
  • 👍 1
More from @goproglib
  1. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  2. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  3. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  4. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
  5. Sep 25, 2026💡 Код работает. А data race уже есть В Go можно записать значение в одной горутине, прочи…
  6. Sep 23, 2026💥 TCP/IP: что происходит с данными в сети Когда Go-приложение отправляет данные по сети,…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →