← Назад к вопросам

Что такое Mock?

2.0 Middle🔥 211 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Что такое Mock

Mock — это имитация (подделка) объекта, которая используется в тестировании. Вместо реального объекта ты создаёшь фальшивый, который ведёт себя так, как тебе нужно для теста.

Основная идея

Вместо: реальная БД → HTTP запросы → файлы → кеш
Мок:    имитирует ответ, не обращаясь к реальной системе

Зачем нужны моки

1. Изолировать код

public class UserService {
    private UserRepository repository;  // Это то, что мокируем
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public User getUserById(Long id) {
        return repository.findById(id);
    }
}

// Без мока:
UserService service = new UserService(new RealDatabase());
// Тест медленный (подключение к БД), зависит от БД, может упасть

// С моком:
UserRepository mockRepo = Mockito.mock(UserRepository.class);
UserService service = new UserService(mockRepo);
// Тест быстрый, независимый от БД, предсказуемый

2. Тестировать сложное взаимодействие

public class PaymentService {
    private PaymentGateway gateway;      // Внешний API
    private UserRepository userRepo;
    private EmailService emailService;
    
    public void processPayment(User user, Order order) {
        // Нельзя вызвать реальный API в тестах!
        gateway.charge(order.getAmount());
        userRepo.updateBalance(user.getId());
        emailService.sendReceipt(user.getEmail());
    }
}

// С моками:
PaymentGateway mockGateway = Mockito.mock(PaymentGateway.class);
UserRepository mockRepo = Mockito.mock(UserRepository.class);
EmailService mockEmail = Mockito.mock(EmailService.class);

PaymentService service = new PaymentService(mockGateway, mockRepo, mockEmail);
// Теперь тестируем логику без реальных вызовов

Типы test doubles

1. Mock (проверяем вызовы)

// Mock проверяет, что нужные методы были вызваны
UserRepository mockRepo = Mockito.mock(UserRepository.class);

UserService service = new UserService(mockRepo);
service.registerUser(new User("John"));

// Проверяем, что save был вызван
Mockito.verify(mockRepo).save(Mockito.any(User.class));

2. Stub (возвращаем фиксированные значения)

// Stub возвращает заранее подготовленные данные
UserRepository stubRepo = Mockito.mock(UserRepository.class);
User testUser = new User("John");

// Когда вызывают findById(1), возвращаем testUser
Mockito.when(stubRepo.findById(1L)).thenReturn(testUser);

UserService service = new UserService(stubRepo);
User result = service.getUserById(1L);

assertThat(result).isEqualTo(testUser);

3. Spy (частичный mock — оборачиваем реальный объект)

// Spy — это реальный объект, но некоторые методы можно "подменить"
UserRepository realRepo = new RealUserRepository();
UserRepository spyRepo = Mockito.spy(realRepo);

// Этот метод работает как реальный
spyRepo.findAll();  // вызывает БД

// Этот метод мокируем
Mockito.when(spyRepo.findById(999L)).thenReturn(null);

// 999 вернёт null, а остальные IDs будут из реальной БД

Практический пример с Mockito

import org.mockito.Mockito;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

public class UserServiceTest {
    
    @Test
    public void testGetUserById() {
        // 1. ARRANGE (подготовка)
        UserRepository mockRepo = Mockito.mock(UserRepository.class);
        User testUser = new User(1L, "John", "john@example.com");
        
        // Говорим моку: когда вызовут findById(1), верни testUser
        Mockito.when(mockRepo.findById(1L)).thenReturn(testUser);
        
        UserService service = new UserService(mockRepo);
        
        // 2. ACT (действие)
        User result = service.getUserById(1L);
        
        // 3. ASSERT (проверка)
        assertThat(result).isEqualTo(testUser);
        assertThat(result.getName()).isEqualTo("John");
    }
    
    @Test
    public void testRegisterUser() {
        // ARRANGE
        UserRepository mockRepo = Mockito.mock(UserRepository.class);
        UserService service = new UserService(mockRepo);
        User newUser = new User(null, "Jane", "jane@example.com");
        
        // ACT
        service.registerUser(newUser);
        
        // ASSERT: проверяем, что save был вызван с нужным user
        Mockito.verify(mockRepo).save(newUser);
        
        // Или более гибко:
        Mockito.verify(mockRepo).save(Mockito.argThat(
            user -> "jane@example.com".equals(user.getEmail())
        ));
    }
    
    @Test
    public void testUserNotFound() {
        // ARRANGE
        UserRepository mockRepo = Mockito.mock(UserRepository.class);
        Mockito.when(mockRepo.findById(999L)).thenReturn(null);
        
        UserService service = new UserService(mockRepo);
        
        // ACT
        User result = service.getUserById(999L);
        
        // ASSERT
        assertThat(result).isNull();
    }
    
    @Test
    public void testExceptionHandling() {
        // ARRANGE
        UserRepository mockRepo = Mockito.mock(UserRepository.class);
        Mockito.when(mockRepo.findById(Mockito.any()))
            .thenThrow(new DatabaseException("Connection failed"));
        
        UserService service = new UserService(mockRepo);
        
        // ACT & ASSERT
        assertThatThrownBy(() -> service.getUserById(1L))
            .isInstanceOf(DatabaseException.class)
            .hasMessage("Connection failed");
    }
}

Аннотации Mockito

import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)  // Активирует @Mock и @InjectMocks
public class UserServiceTest {
    
    @Mock
    private UserRepository mockRepo;  // Автоматически создаёт mock
    
    @Mock
    private EmailService mockEmail;
    
    @InjectMocks
    private UserService service;  // Автоматически внедряет моки в service
    
    @Test
    public void testRegister() {
        // mockRepo и mockEmail уже готовы
        // service уже получил их в конструктор/setter
        
        Mockito.when(mockRepo.save(Mockito.any())).thenReturn(true);
        
        boolean result = service.registerUser(new User());
        
        assertThat(result).isTrue();
        Mockito.verify(mockEmail).sendWelcomeEmail(Mockito.any());
    }
}

Частые случаи использования

1. Задать возвращаемое значение

Mockito.when(mockRepo.findById(1L)).thenReturn(user);
Mockito.when(mockRepo.findAll()).thenReturn(List.of(user1, user2));

2. Выбросить исключение

Mockito.when(mockRepo.save(Mockito.any()))
    .thenThrow(new DatabaseException());

3. Вернуть разные значения при разных вызовах

Mockito.when(mockRepo.findById(1L))
    .thenReturn(user1)       // Первый вызов
    .thenReturn(user2)       // Второй вызов
    .thenThrow(new Exception()); // Третий вызов

4. Проверить, сколько раз был вызван метод

Mockito.verify(mockRepo, Mockito.times(2)).findById(1L);
Mockito.verify(mockRepo, Mockito.never()).deleteById(1L);
Mockito.verify(mockRepo, Mockito.atLeast(1)).save(Mockito.any());

5. Проверить аргументы

// Точное совпадение
Mockito.verify(mockEmail).sendEmail("user@example.com");

// С любым аргументом
Mockito.verify(mockRepo).save(Mockito.any(User.class));

// С условием
Mockito.verify(mockRepo).save(
    Mockito.argThat(user -> user.getEmail().contains("@"))
);

Mock vs Stub vs Real (когда что использовать)

СлучайВыборПример
Проверяем вызовы методовMockverify(mock).method()
Нужны фиксированные ответыStubwhen(stub.method()).thenReturn(value)
Нужно реальное поведениеReal objectИспользуем реальный класс
Нужно 90% реального + 10% мокаSpyspy(realObject)

Когда НЕ использовать моки

❌ Для тестирования utility функций (без dependencies) ❌ Для всего подряд (замедляет тесты и усложняет) ❌ Вместо правильной архитектуры (если много моков = плохой дизайн)

Best practices

// ✅ ХОРОШО: мокируем только зависимости
@Test
public void testBusinessLogic() {
    UserRepository mockRepo = Mockito.mock(UserRepository.class);
    User user = new User(); // реальный объект, не мок
    
    service.process(user);
}

// ❌ ПЛОХО: мокируем всё
@Test
public void testBusinessLogic() {
    User mockUser = Mockito.mock(User.class);
    UserRepository mockRepo = Mockito.mock(UserRepository.class);
    // Теперь тестируем тесты, а не код
}

Итог

Mock — это подставной объект, который:

  • Имитирует поведение реального объекта
  • Позволяет тестировать в изоляции (без БД, API, файлов)
  • Позволяет проверить, были ли вызваны нужные методы
  • Делает тесты быстрыми и предсказуемыми

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

Что такое Mock? | PrepBro