Мокирование данных - базовые и продвинутые техники для надежных тестов

19 февраля 2026
Автор

Олег Марков

Введение

Мокирование данных (mocking) — это техника, которая позволяет вам подменять реальные зависимости в коде на «фальшивые» версии во время тестирования. Смотрите, идея очень простая: вместо того чтобы ваш тест ходил в реальную базу данных, вы подсовываете ему объект, который ведет себя «как база», но ничего не сохраняет и не читает по‑настоящему.

Давайте сразу зафиксируем, зачем это нужно:

  • изолировать тестируемый код от внешних систем (БД, сетевые сервисы, файловая система);
  • сделать тесты быстрыми и стабильными;
  • проверять не только результат, но и то, как ваш код взаимодействует с зависимостями (какие методы вызываются, с какими аргументами).

В этой статье мы разберем основные виды моков, отличия от других тестовых двойников (stub, fake, spy), общие принципы проектирования кода под мокирование и посмотрим на конкретные примеры, как это делается на практике (на примере Go и немного псевдокода, чтобы было понятно независимо от языка).

Что такое мокирование данных

Тестовые двойники и их роль

Чтобы лучше понять mocking, важно различать несколько типов тестовых объектов. В литературе их называют «тестовыми двойниками» (test doubles). Давайте кратко их перечислим:

  • Dummy — заглушка, которая существует просто чтобы занять аргумент метода (но не используется).
  • Stub — объект, который возвращает заранее подготовленные данные, но не проверяет, как его вызвали.
  • Fake — упрощенная реализация, обычно с «мини‑логикой» (например, in‑memory база вместо реальной).
  • Spy — объект, который запоминает, как его вызывали (аргументы, количество вызовов).
  • Mock — объект, который не только имитирует поведение, но и проверяет ожидания: какие методы должны быть вызваны и сколько раз.

Смотрите, я покажу вам простое разделение: mocks — это про «проверку взаимодействия», stubs — про «подставить данные». На практике инструменты mocking‑фреймворков часто позволяют делать и то, и другое, поэтому границы немного размыты. Но концептуально разница именно такая.

Когда использовать мокирование

Мокирование особенно полезно, когда:

  • есть внешние зависимости:
    • база данных;
    • REST/GraphQL API;
    • message queue;
    • файловая система;
    • кэш (Redis, Memcached);
  • зависимости нестабильны или недоступны в тестовой среде;
  • вы хотите протестировать сложную бизнес‑логику, а не интеграцию с внешними системами;
  • важно проверить сам факт вызова методов (например, что при ошибке отправляется уведомление).

Но при этом чрезмерное мокирование вредит:

  • тесты становятся «хрупкими» — любое изменение внутренней реализации ломает ожидаемые вызовы;
  • тесты начинают проверять детали реализации, а не поведение;
  • код переполнен интерфейсами «ради тестов».

Чуть позже мы обсудим, как избежать таких проблем.

Базовые принципы мокирования

Принцип инверсии зависимостей

Чтобы мокировать что‑то в тестах, вам сначала нужно научиться отделять «что делает код» от «с чем он работает». Обычно это делается через интерфейсы или абстракции.

Представьте сервис:

  • он читает пользователя из базы;
  • отправляет ему письмо;
  • логирует результат.

Если вы сделаете эти зависимости жестко зашитыми внутрь (newDatabaseClient, newMailer и т.п.), вы не сможете подменить их в тесте. Поэтому вам нужен либо конструктор, либо dependency injection.

Давайте разберемся на примере на Go. В других языках идея такая же.

Пример без мокирования (плотные зависимости)

Сначала пример, который мокировать неудобно:

type UserService struct{}

func (s *UserService) Register(email string) error {
    // Здесь мы напрямую создаем клиента БД
    db := NewRealDBClient() // Тесная связь с реальной БД
    // Здесь создаем реальный отправитель почты
    mailer := NewMailer()

    user := &User{Email: email}

    // Сохраняем пользователя
    if err := db.SaveUser(user); err != nil {
        return err
    }

    // Отправляем письмо
    if err := mailer.SendWelcomeEmail(user); err != nil {
        return err
    }

    return nil
}

Комментарии:

// В этом коде невозможно просто так подменить БД и мейлер // Все зависимости создаются прямо внутри метода // Для теста это плохо - он будет зависеть от реальных внешних ресурсов

Такой код сложно тестировать юнит‑тестами: вам придется поднимать настоящую базу и почтовый сервис или городить сложную конфигурацию.

Пример с интерфейсами и внедрением зависимостей

Теперь давайте посмотрим, как изменить дизайн, чтобы его было удобно мокировать:

// Интерфейс работы с пользователями в хранилище
type UserRepository interface {
    SaveUser(user *User) error
}

// Интерфейс для отправки писем
type Mailer interface {
    SendWelcomeEmail(user *User) error
}

type UserService struct {
    repo   UserRepository
    mailer Mailer
}

// Конструктор сервиса - зависимости передаются снаружи
func NewUserService(repo UserRepository, mailer Mailer) *UserService {
    return &UserService{
        repo:   repo,
        mailer: mailer,
    }
}

func (s *UserService) Register(email string) error {
    user := &User{Email: email}

    // Здесь используется абстракция - нам не важно, реальная это БД или мок
    if err := s.repo.SaveUser(user); err != nil {
        return err
    }

    if err := s.mailer.SendWelcomeEmail(user); err != nil {
        return err
    }

    return nil
}

Комментарии:

// Теперь UserService не создает зависимости сам // Вместо этого они приходят через конструктор // В продакшене вы передадите реальные реализации // В тестах - мок-объекты, реализующие те же интерфейсы

Теперь вы легко можете подменить repo и mailer на моки, не затрагивая бизнес‑логику.

Виды моков и подходы к их созданию

Ручные (hand-written) моки

Самый прозрачный способ — написать мок руками. Вы просто создаете структуру (или класс), которая реализует нужный интерфейс, и добавляете в нее поля для фиксации вызовов и возвращаемых значений.

Давайте я покажу вам, как это выглядит:

// Мок репозитория пользователей
type UserRepositoryMock struct {
    SavedUsers []*User // Здесь мы запоминаем, какие пользователи были сохранены
    SaveErr    error   // Ошибка, которую нужно вернуть (если нужна)
}

func (m *UserRepositoryMock) SaveUser(user *User) error {
    // Сохраняем пользователя в список вызовов
    m.SavedUsers = append(m.SavedUsers, user)

    // Возвращаем заранее настроенную ошибку
    return m.SaveErr
}

// Мок отправителя писем
type MailerMock struct {
    SentToUsers []*User
    SendErr     error
}

func (m *MailerMock) SendWelcomeEmail(user *User) error {
    m.SentToUsers = append(m.SentToUsers, user)
    return m.SendErr
}

Комментарии:

// Эти моки ничего не делают снаружи - не ходят в БД и не отправляют письма // Они просто записывают, как их вызывали, и возвращают заранее заданные значения

Теперь вы увидите, как это выглядит в тесте:

func TestUserService_Register_Success(t *testing.T) {
    // Создаем моки
    repoMock := &UserRepositoryMock{}
    mailerMock := &MailerMock{}

    // Собираем сервис с моками
    service := NewUserService(repoMock, mailerMock)

    // Вызываем тестируемый метод
    err := service.Register("test@example.com")
    if err != nil {
        t.Fatalf("ожидали nil ошибку, получили %v", err)
    }

    // Проверяем, что пользователь был сохранен
    if len(repoMock.SavedUsers) != 1 {
        t.Fatalf("ожидали 1 сохраненного пользователя, получили %d", len(repoMock.SavedUsers))
    }

    // Проверяем, что было отправлено письмо
    if len(mailerMock.SentToUsers) != 1 {
        t.Fatalf("ожидали 1 отправленное письмо, получили %d", len(mailerMock.SentToUsers))
    }
}

Комментарии:

// Здесь мы используем моки, чтобы проверить взаимодействие // Мы не проверяем напрямую базу или почтовый сервис // Мы проверяем, что бизнес-логика вызвала нужные методы

Преимущества ручных моков:

  • очень прозрачны и понятны;
  • легко контролировать логику;
  • меньше магии и зависимостей.

Недостатки:

  • много шаблонного кода;
  • для большого числа интерфейсов писать моки руками утомительно.

Генерируемые моки (mocking frameworks)

Во многих языках есть фреймворки, которые позволяют:

  • автоматически генерировать моки по интерфейсу (Go: gomock, testify/mock; Java: Mockito; JS: Jest и т.д.);
  • на лету конфигурировать ожидаемые вызовы и возвращаемые значения;
  • проверять, что ожидания были выполнены.

Покажу вам упрощенный пример с популярным стилем «ожидание → вызов → проверка» (псевдокод, но он похож на Go/Java‑стиль):

// pseudo-code, стиль похож на testify/mock

// В тесте мы создаем мок-объект
repoMock := NewUserRepositoryMock(t)
mailerMock := NewMailerMock(t)

// Настраиваем ожидания
repoMock.
    On("SaveUser", mock.AnythingOfType("*User")). // Ждем вызов с аргументом типа *User
    Return(nil).                                  // Вернем nil-ошибку

mailerMock.
    On("SendWelcomeEmail", mock.Anything). // Любой пользователь
    Return(nil)

// Собираем сервис
service := NewUserService(repoMock, mailerMock)

// Вызываем метод
err := service.Register("test@example.com")
require.NoError(t, err)

// Проверяем, что все ожидания по мокам выполнены
repoMock.AssertExpectations(t)
mailerMock.AssertExpectations(t)

Комментарии:

// Мы заранее задаем "контракт" - какие методы должны быть вызваны // После выполнения теста фреймворк сам проверит, что ожидания выполнены // Если метод не был вызван или был вызван с другими аргументами - тест упадет

Плюсы:

  • меньше ручного кода;
  • удобно конфигурировать разные сценарии;
  • часто есть «умные» матчеры аргументов (any, regexp, custom‑matcher).

Минусы:

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

Fake‑объекты как альтернатива мокам

Иногда вместо моков удобнее использовать fake — упрощенную реализацию.

Например, вы пишете интерфейс UserRepository:

type UserRepository interface {
    SaveUser(user *User) error
    FindByEmail(email string) (*User, error)
}

Для интеграционных тестов можно сделать in‑memory реализацию:

type InMemoryUserRepository struct {
    users map[string]*User
}

func NewInMemoryUserRepository() *InMemoryUserRepository {
    return &InMemoryUserRepository{
        users: make(map[string]*User),
    }
}

func (r *InMemoryUserRepository) SaveUser(user *User) error {
    // Сохраняем пользователя в карту по email
    r.users[user.Email] = user
    return nil
}

func (r *InMemoryUserRepository) FindByEmail(email string) (*User, error) {
    user, ok := r.users[email]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}

Комментарии:

// Это рабочая реализация, но она использует память вместо реальной БД // В тестах такая реализация ведет себя "как база", но не требует окружения // Вы тестируете больше логики сразу, чем с моками

Такие fake‑объекты часто удобнее, чем моки, когда:

  • у вас сложный сценарий работы с хранилищем;
  • хочется протестировать больше реальной логики;
  • не требуется проверять точные вызовы методов.

Как проектировать код, который легко мокировать

Используйте интерфейсы для внешних зависимостей

Ключевая идея: все, что выходит «наружу» — в сеть, в файловую систему, в БД, во время — хорошо выносить за интерфейсы.

Примеры зависимостей, которые обычно оборачивают в интерфейсы:

  • HTTP‑клиент;
  • клиент БД;
  • провайдер текущего времени;
  • генератор UUID;
  • сервис отправки писем/смс.

Например, с текущим временем:

// Интерфейс поставщика времени
type Clock interface {
    Now() time.Time
}

// Реальная реализация, использующая time.Now
type RealClock struct{}

func (RealClock) Now() time.Time {
    return time.Now()
}

Комментарии:

// В продакшене вы будете использовать RealClock // В тестах вы создадите мок Clock, который возвращает фиксированное время // Так ваши тесты станут детерминированными

Разделяйте бизнес‑логику и инфраструктуру

Старайтесь, чтобы ваши доменные сервисы (бизнес‑логика) были как можно более «чистыми»:

  • минимум знаний о конкретной базе;
  • минимум зависимостей от HTTP;
  • без привязки к фреймворкам.

Инфраструктура (настройка HTTP‑роутов, соединения с базой, конфигурация) пусть живет отдельно, например в main‑пакете или отдельных сборочных модулях.

Это делает тесты проще: вы можете протестировать доменную логику отдельно, подменив все инфраструктурные зависимости на моки.

Не мокируйте то, что можно протестировать напрямую

Обратите внимание на такой момент: многие новички начинают мокировать вообще все, включая простые структуры данных или «чистые» функции.

Если функция:

  • не обращается наружу;
  • детерминирована (результат зависит только от аргументов);

то мокировать ее не нужно. Ее можно (и нужно) тестировать напрямую, обычными unit‑тестами.

Мокирование — это инструмент для изоляции от внешнего мира. Чем меньше внешних зависимостей, тем меньше моков.

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

1. Мокирование HTTP‑клиента

Представьте сервис, который обращается к внешнему API:

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

type ExternalAPI struct {
    client HTTPClient
}

func NewExternalAPI(client HTTPClient) *ExternalAPI {
    return &ExternalAPI{client: client}
}

func (api *ExternalAPI) GetUserScore(userID string) (int, error) {
    // Собираем запрос
    req, err := http.NewRequest("GET", "https://example.com/score?user="+userID, nil)
    if err != nil {
        return 0, err
    }

    // Отправляем запрос через абстрактный клиент
    resp, err := api.client.Do(req)
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return 0, fmt.Errorf("unexpected status %d", resp.StatusCode)
    }

    // Здесь мы читаем тело и парсим его, опустим это для краткости
    // ...

    return 42, nil // Допустим, распарсили такой результат
}

В тесте вы можете замокировать HTTPClient:

type HTTPClientMock struct {
    LastRequest *http.Request
    Response    *http.Response
    Err         error
}

func (m *HTTPClientMock) Do(req *http.Request) (*http.Response, error) {
    // Сохраняем последний запрос для проверки
    m.LastRequest = req
    // Возвращаем заранее подготовленный ответ и ошибку
    return m.Response, m.Err
}

Теперь давайте проверим позитивный сценарий:

func TestExternalAPI_GetUserScore_Success(t *testing.T) {
    // Готовим фейковое тело ответа
    body := io.NopCloser(strings.NewReader(`{"score": 42}`))

    // Готовим ответ
    resp := &http.Response{
        StatusCode: http.StatusOK,
        Body:       body,
    }

    clientMock := &HTTPClientMock{
        Response: resp,
    }

    api := NewExternalAPI(clientMock)

    score, err := api.GetUserScore("123")
    if err != nil {
        t.Fatalf("ожидали nil ошибку, получили %v", err)
    }

    if score != 42 {
        t.Fatalf("ожидали score 42, получили %d", score)
    }

    // Проверим, что запрос собирался с нужным URL
    if clientMock.LastRequest == nil {
        t.Fatalf("ожидали, что запрос был отправлен")
    }

    if clientMock.LastRequest.URL.Query().Get("user") != "123" {
        t.Fatalf("ожидали user=123, получили %s",
            clientMock.LastRequest.URL.Query().Get("user"))
    }
}

Комментарии:

// Мы полностью контролируем ответ от внешнего API // Тест ничего не знает о сети и не требует интернета // При этом мы проверяем и логику, и правильность сформированного запроса

2. Мокирование ошибок и нестабильных сценариев

Моки особенно удобны, чтобы симулировать ошибки: падение БД, таймауты, исключения. Реализовать такие ситуации с реальной системой часто сложно.

Представим, что метод Register должен откатить сохранение пользователя, если отправка письма не удалась. В тесте мы можем сделать так:

func TestUserService_Register_FailOnEmail(t *testing.T) {
    repoMock := &UserRepositoryMock{}
    mailerMock := &MailerMock{
        // Заставляем отправку письма падать
        SendErr: errors.New("smtp error"),
    }

    service := NewUserService(repoMock, mailerMock)

    err := service.Register("test@example.com")
    if err == nil {
        t.Fatalf("ожидали ошибку, получили nil")
    }

    // Проверим, что пользователь был сохранен
    if len(repoMock.SavedUsers) != 1 {
        t.Fatalf("ожидали 1 сохраненного пользователя, получили %d", len(repoMock.SavedUsers))
    }

    // В реальной логике можно добавить откат, логирование и т.д.
    // Моки позволят проверить каждую ветку поведения
}

Комментарии:

// Мы легко моделируем ошибку отправки письма // Для настоящего SMTP сервера это сделать сложнее // Так вы можете покрыть логикой обработки ошибок почти все кейсы

3. Частичное мокирование и spies

Иногда вам нужно не полностью подменить реализацию, а только «подсмотреть», как объект используется. В таких случаях применяют spies или частичные моки.

Например, вы хотите убедиться, что при определенном сценарии вызывается метод LogError.

В ручном стиле это может выглядеть так:

type Logger interface {
    Info(msg string)
    Error(msg string)
}

type LoggerSpy struct {
    InfoMessages  []string
    ErrorMessages []string
}

func (l *LoggerSpy) Info(msg string) {
    l.InfoMessages = append(l.InfoMessages, msg)
}

func (l *LoggerSpy) Error(msg string) {
    l.ErrorMessages = append(l.ErrorMessages, msg)
}

Комментарии:

// LoggerSpy ничего не пишет в stdout или файл // Он просто запоминает сообщения // В тесте вы проверите, что Error был вызван с нужным текстом

В тесте:

func TestService_LogsErrorOnFailure(t *testing.T) {
    logger := &LoggerSpy{}
    // Передаем logger в сервис вместе с другими зависимостями
    // ...

    // Вызовем метод, который должен логировать ошибку
    // ...

    if len(logger.ErrorMessages) == 0 {
        t.Fatalf("ожидали хотя бы одно сообщение об ошибке")
    }
}

Типичные ошибки при использовании mocking

1. Мокирование деталей реализации вместо поведения

Чрезмерное количество ожиданий в моках делает тесты хрупкими.

Например, вы пишете такой тест:

  • ожидаете, что метод SaveUser вызывается первым;
  • потом ожидаете, что SendWelcomeEmail вызывается вторым;
  • затем проверяете конкретный текст логов.

Если в будущем вы:

  • решите отправлять письмо до сохранения;
  • добавите промежуточные проверки;

тест сломается, хотя внешнее поведение сервиса может оставаться корректным.

Рекомендации:

  • фиксируйте только то, что действительно важно для контракта;
  • старайтесь проверять результат работы, а не порядок внутренних вызовов;
  • избегайте проверки «лишних» логов, если они не часть контракта.

2. Слишком много интерфейсов ради мокирования

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

Хороший критерий: выносите в интерфейсы только те зависимости, которые:

  • внешние по отношению к домену;
  • могут иметь разные реализации;
  • логично представляет собой «контракт».

Не нужно делать интерфейс для структуры с 1 методом, если она и так чисто вычисляет значение без внешних эффектов.

3. Смешивание unit‑тестов и интеграционных тестов

Иногда моки используются там, где логичнее написать интеграционный тест с реальной системой:

  • например, проверить, что SQL‑запросы к базе действительно работают;
  • или что REST‑endpoint корректно интегрирован с бекендом.

Моки позволяют писать быстрые и изолированные тесты, но они не гарантируют, что ваша интеграция с реальной системой будет работать. Поэтому:

  • для логики — используйте моки;
  • для интеграции — пишите отдельные тесты с реальными зависимостями (по возможности в отдельном тестовом окружении).

4. Сложные моки с логикой

Еще один нюанс: если ваш мок начинает содержать сложную логику, условия, циклы и т.д., это сигнал, что вы, возможно, реализуете второй «мини‑продакшен» внутри моков. Это:

  • усложняет поддержку тестов;
  • создает риск расхождения между моками и реальной реализацией.

В такой ситуации лучше:

  • либо упростить моки;
  • либо создать fake‑реализацию, близкую к настоящей, и использовать ее как тестовую инфраструктуру.

Рекомендации по организации тестов с моками

Где хранить моки

Практично выносить моки:

  • в отдельные файлы с суффиксом _mock или mock_ в имени;
  • в отдельный пакет, если моки используются в нескольких модулях.

Это помогает:

  • не засорять файлы с продакшен‑кодом;
  • переиспользовать моки между тестами.

Например:

  • user_service.go — код сервиса;
  • userservicetest.go — тесты сервиса;
  • usermockstest.go — моки для тестов.

Именование

Старайтесь давать мокам понятные имена:

  • UserRepositoryMock;
  • HTTPClientMock;
  • LoggerSpy;
  • InMemoryUserRepository (для fake).

Так по имени сразу понятно, какую роль играет объект.

Повторное использование и настройка

Хорошая практика — делать моки настраиваемыми:

  • можно задать возвращаемую ошибку;
  • можно подготовить список ответов по очереди;
  • можно указывать поведение (успех/ошибка) для разных аргументов.

Например:

type UserRepositoryMock struct {
    // Отображение email -> ошибка
    SaveErrors map[string]error
}

func (m *UserRepositoryMock) SaveUser(user *User) error {
    if m.SaveErrors != nil {
        if err, ok := m.SaveErrors[user.Email]; ok {
            return err
        }
    }
    return nil
}

Комментарии:

// В разных тестах вы сможете задавать разные сценарии // Для одного пользователя SaveUser будет падать, для другого - проходить успешно

Заключение

Мокирование данных — это ключевая техника для построения быстрых и надежных юнит‑тестов. Суть подхода в том, чтобы временно подменять реальные зависимости вашего кода на тестовые двойники:

  • моки — для проверки взаимодействий;
  • стабы — для подстановки данных;
  • fake‑объекты — для упрощенной, но рабочей логики;
  • spies — для наблюдения за вызовами.

Чтобы мокирование было удобным, код нужно проектировать с учетом:

  • инверсии зависимостей;
  • разделения бизнес‑логики и инфраструктуры;
  • четких интерфейсов для внешних систем.

Моки позволяют:

  • изолировать тестируемый модуль;
  • моделировать ошибки и редкие сценарии;
  • ускорить тесты за счет отказа от реальных внешних ресурсов.

При этом важно не перегибать:

  • не мокировать все подряд;
  • не завязывать тесты на мелкие детали реализации;
  • не превращать моки во вторую сложную систему.

Если вы будете использовать mocking осознанно, то получите тесты, которые:

  • быстро выполняются;
  • легко читаются;
  • хорошо документируют ожидаемое поведение системы.

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как замокировать время чтобы тесты не зависели от текущей даты

Сделайте интерфейс Clock с методом Now и используйте его вместо прямого вызова текущего времени. В проде передавайте реальную реализацию, в тестах — мок, который возвращает фиксированное время. Например, в тесте создайте структуру FixedClock со свойством Time и реализуйте метод Now, возвращающий это значение. Так вы сможете воспроизводимо проверять сценарии с дедлайнами и просрочками.

Как протестировать код который создает зависимости внутри себя и не принимает их снаружи

Первый вариант — рефакторинг под dependency injection и введение интерфейсов. Если это временно невозможно, можно использовать паттерн «переопределяемые фабрики» — вынести создание зависимости в функцию‑переменную и в тестах переопределять ее на мок‑функцию. Например, var newDB = NewRealDBClient, а в тесте подменить newDB на функцию, возвращающую мок.

Как мокировать функции без интерфейса например глобальные утилиты

Если язык поддерживает подмену функций на время теста (как в JavaScript), можно временно заменить реализацию. В строгих языках чаще всего: оборачивают функцию в интерфейс и прокидывают зависимость или используют шаблон «фасад» — структура с методами обертками над функциями. Тогда в тестах вы подменяете фасад на мок, а не трогаете глобальную функцию напрямую.

Когда лучше использовать fake вместо моков

Fake удобен, когда нужно больше реальной логики и последовательных операций. Например, in‑memory реализация репозитория для сложных сценариев сохранения и выборки. Если вы видите, что моки становятся слишком сложными и начинают хранить состояние и условия, лучше вынести это в отдельную fake‑реализацию, которая будет вести себя максимально похоже на настоящую, но без внешних зависимостей.

Как не запутаться в большом количестве моков в проекте

Разделите моки по уровням и областям: храните интерфейсные моки в отдельных пакетах (например, internal/mocks или pkg/testsupport), давайте им однозначные имена и не смешивайте их с продакшен‑кодом. Используйте базовые фабрики для типовых конфигураций моков (например, NewSuccessfulUserRepoMock). Если моки начинают дублировать друг друга, вынесите общую часть в один общий мок и добавляйте поверх него небольшие настройки в конкретных тестах.

Стрелочка влевоСостояние приложения - state-management в веб разработкеРабота с API - api-integration пошаговое руководство с примерамиСтрелочка вправо

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий