Java-фреймворк для mock-тестирования Mockito

Java 常用类库Стандартные библиотеки классов Java


Что такое mock-тестирование?

В разработке программного обеспечения модульное тестирование является важным средством проверки функциональности кода. Однако когда наш код зависит от внешних систем (таких как базы данных, веб-сервисы и т. д.), прямое тестирование может столкнуться с несколькими проблемами:

  1. Внешние зависимости могут быть нестабильными или недоступными
  2. Скорость выполнения тестов становится ниже
  3. Сложно смоделировать различные граничные условия

Mock-тестирование (имитационное тестирование) решает эти проблемы, создавая «заменители» для объектов. Mock-объекты могут:

  • Имитировать поведение реальных объектов
  • Проверять, происходят ли взаимодействия ожидаемым образом
  • Не выполнять фактическую логику реальных объектов

Почему выбрать Mockito?

Mockito в настоящее время является самым популярным фреймворком для Mock-тестирования в экосистеме Java и обладает следующими преимуществами:

  1. Лаконичный API: плавная кривая обучения, простой старт
  2. Мощная функциональность: поддерживает проверку вызовов методов, настройку возвращаемых значений, выбрасывание исключений и т. д.
  3. Хорошая читаемость: тестовый код интуитивно понятен и прост для понимания
  4. Активное сообщество: постоянно обновляется и поддерживается, имеет исчерпывающую документацию

Основные концепции Mockito

1. Создание Mock-объектов

Пример

// Способ 1: использовать статический метод
List mockedList = Mockito.mock(List.class);

// Способ 2: использовать аннотации (требуется MockitoJUnitRunner)
@Mock
List mockedList;

2. Настройка поведения методов

Пример

// Вернуть 100, когда вызывается mockedList.size()
when(mockedList.size()).thenReturn(100);

// Вернуть "first", когда вызывается mockedList.get(0)
when(mockedList.get(0)).thenReturn("first");

// Выбросить исключение, когда вызывается mockedList.get(1)
when(mockedList.get(1)).thenThrow(new RuntimeException());

3. Проверка взаимодействий

Пример

// Проверить, что mockedList.add("one") был вызван один раз
verify(mockedList).add("one");

// Проверить, что mockedList.clear() никогда не вызывался
verify(mockedList, never()).clear();

// Проверить, что mockedList.add("two") вызывался как минимум дважды
verify(mockedList, atLeast(2)).add("two");

Практические примеры применения Mockito

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

Предположим, у нас естьUserServiceзависит отUserRepository:

Пример

public class UserService {
    private UserRepository userRepository;
   
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
   
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("Пользователь не найден"));
    }
}

Используйте Mockito для тестирования:

Пример

public class UserServiceTest {
    @Mock
    private UserRepository userRepository;
   
    @InjectMocks
    private UserService userService;
   
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }
   
    @Test
    public void testGetUserById_Success() {
        // Подготовка тестовых данных
        User mockUser = new User(1L, "testUser");
       
        // Настроить поведение мока
        when(userRepository.findById(1L))
            .thenReturn(Optional.of(mockUser));
       
        // Выполнить тест
        User result = userService.getUserById(1L);
       
        // Проверить результат
        assertEquals("testUser", result.getUsername());
        verify(userRepository).findById(1L);
    }
   
    @Test(expected = UserNotFoundException.class)
    public void testGetUserById_NotFound() {
        when(userRepository.findById(2L))
            .thenReturn(Optional.empty());
       
        userService.getUserById(2L);
    }
}

Продвинутые возможности Mockito

1. Матчеры аргументов

Пример

// Любой целочисленный аргумент
when(mockedList.get(anyInt())).thenReturn("element");

// Аргумент определённого типа
when(mockedList.contains(anyString())).thenReturn(истина);

// Пользовательский матчер
when(mockedList.add(argThat(arg -> arg.length() > 5))).thenReturn(истина);

2. Проверка порядка вызовов

Пример

InOrder inOrder = inOrder(mockedList);

inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");

3. Частичный мок (Spy)

Пример

List realList = new ArrayList();
List spyList = spy(realList);

// Вызвать реальный метод
spyList.add("real");

// Замокировать конкретный метод
doReturn(100).when(spyList).size();

Лучшие практики

  1. Не злоупотребляйте моками: Создавайте моки только для необходимых зависимостей, делайте тесты реалистичными
  2. Проверяйте взаимодействия в меру: Сосредоточьтесь на важных взаимодействиях, не проверяйте каждый вызов метода
  3. Делайте тесты краткими: каждый тестовый метод должен проверять только одну функциональную точку
  4. Разумно организуйте тестовый код: используйте @Before для общей настройки
  5. Сочетайте с другими инструментами тестирования: используйте вместе с JUnit, AssertJ и т. д.

Часто задаваемые вопросы (FAQ)

Вопрос: В чём разница между Mockito и PowerMock?

Ответ: Mockito в основном используется для мокирования обычных объектов, тогда как PowerMock может мокировать статические методы, конструкторы и т. д. Однако PowerMock ухудшает изоляцию тестов, поэтому рекомендуется сначала использовать Mockito.

Вопрос: Когда следует использовать Spy вместо Mock?

Ответ: Используйте Spy, когда вам нужно в основном реальное поведение и нужно изменить лишь несколько методов. Если вам нужен полный контроль над поведением объекта, используйте Mock.

Вопрос: Можно ли использовать Mockito для final-классов или методов?

Ответ: Начиная с Mockito 2.1.0, final-классы и методы можно мокировать через конфигурацию, но вам нужно добавитьmockito-inlineзависимость.


Связанные ссылки

  1. Официальная документация Mockito
  2. Репозиторий Mockito на GitHub

Java 常用类库Стандартные библиотеки классов Java

Другие расширения