Как писать мок-логику в юнит тестах
Что проверяют
Понимаете ли вы, что мок в Go начинается с интерфейса, а не с библиотеки. Следом спрашивают, где этот интерфейс объявлять и когда мок хуже настоящей зависимости.
Как отвечать
Тестируемый код зависит от интерфейса, а не от конкретной структуры. Интерфейс объявляется на стороне потребителя и содержит только нужные ему методы, по принципу «принимай интерфейсы, возвращай структуры». Тогда подменить зависимость можно чем угодно.
Варианты от простого к сложному:
- ручной фейк: структура с полями-функциями, например
GetUserFunc func(ctx context.Context, id int64) (User, error). Никаких библиотек, поведение видно прямо в тесте. Для интерфейса из одного-двух методов это обычно лучший выбор; - gomock:
mockgenгенерирует мок, в тестеctrl := gomock.NewController(t), ожидания черезEXPECT().Method(gomock.Any()).Return(...), число вызовов черезTimes(). Оригинальныйgithub.com/golang/mockзаархивирован, поддерживается форкgo.uber.org/mock. Вызыватьctrl.Finish()при передачеtне нужно, проверка регистрируется черезt.Cleanup; - testify:
mockeryгенерирует моки наmock.Mock, ожидания задают черезOn("Method", args).Return(...), проверяют черезAssertExpectations(t); - настоящие стенды вместо моков: для HTTP-клиентов
httptest.NewServer, для репозиториев база в testcontainers-go. SQL-запрос мок не проверит, он вернёт ровно то, что вы в него положили.
Генерацию закрепляют директивой //go:generate рядом с интерфейсом, а тесты оформляют таблицей кейсов, где у каждого кейса своя настройка мока.
Где спотыкаются
Объявляют большой интерфейс на стороне реализации и мокают все двадцать методов. Тесты становятся хрупкими, а интерфейс просто копирует структуру.
Проверяют каждый вызов и его порядок. Тест фиксирует реализацию, а не поведение, и ломается на любом рефакторинге.
Мокают базу в тестах репозитория. Тест зелёный, а запрос с опечаткой в имени колонки падает при первом запуске на настоящей базе.