Home » Mockito Mock для void методов — пример использования
Mockito Mock для void методов — пример использования

Mockito Mock для void методов — пример использования

Если ты хотя бы раз занимался серьёзной Java-разработкой, то наверняка сталкивался с ситуацией, когда нужно покрыть тестами void методы. Казалось бы, что там тестировать? Метод ничего не возвращает, но побочные эффекты могут быть самыми разными — от записи в базу данных до отправки уведомлений. Вот тут-то и приходит на помощь Mockito с его мощными возможностями для работы с void методами. Эта статья поможет тебе разобраться с тонкостями мокирования таких методов, избежать типичных ошибок и построить надёжные тесты для твоих серверных приложений.

Как работает мокирование void методов

Mockito обрабатывает void методы несколько иначе, чем обычные методы с возвращаемыми значениями. Основная разница в том, что вместо классического when().thenReturn() используется конструкция doAction().when(). Это связано с тем, что компилятор Java не может определить тип возвращаемого значения для void методов в цепочке when().

Основные подходы к работе с void методами:

  • doNothing() — метод выполняется без побочных эффектов
  • doThrow() — метод бросает исключение
  • doAnswer() — кастомная логика выполнения
  • doCallRealMethod() — вызов реального метода

Пошаговая настройка тестового окружения

Начнём с создания базового проекта. Для работы с Mockito понадобится добавить зависимость в Maven или Gradle:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>

Создадим простой сервис для работы с пользователями:

public class UserService {
    private final EmailService emailService;
    private final DatabaseService databaseService;
    
    public UserService(EmailService emailService, DatabaseService databaseService) {
        this.emailService = emailService;
        this.databaseService = databaseService;
    }
    
    public void createUser(String email, String name) {
        User user = new User(email, name);
        databaseService.saveUser(user);
        emailService.sendWelcomeEmail(email, name);
    }
    
    public void deleteUser(String email) {
        databaseService.deleteUser(email);
        emailService.sendGoodbyeEmail(email);
    }
}

И соответствующие зависимости:

public interface EmailService {
    void sendWelcomeEmail(String email, String name);
    void sendGoodbyeEmail(String email);
}

public interface DatabaseService {
    void saveUser(User user);
    void deleteUser(String email);
}

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

Теперь создадим тесты для нашего сервиса:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private EmailService emailService;
    
    @Mock
    private DatabaseService databaseService;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void shouldCreateUserSuccessfully() {
        // Given
        String email = "test@example.com";
        String name = "John Doe";
        
        // When
        userService.createUser(email, name);
        
        // Then
        verify(databaseService).saveUser(any(User.class));
        verify(emailService).sendWelcomeEmail(email, name);
    }
    
    @Test
    void shouldHandleDatabaseException() {
        // Given
        String email = "test@example.com";
        String name = "John Doe";
        
        doThrow(new RuntimeException("Database error"))
            .when(databaseService).saveUser(any(User.class));
        
        // When & Then
        assertThrows(RuntimeException.class, () -> {
            userService.createUser(email, name);
        });
        
        verify(databaseService).saveUser(any(User.class));
        verify(emailService, never()).sendWelcomeEmail(anyString(), anyString());
    }
}

Продвинутые техники и кейсы

Рассмотрим более сложные сценарии использования:

@Test
void shouldExecuteMethodsInCorrectOrder() {
    // Given
    String email = "test@example.com";
    
    // When
    userService.deleteUser(email);
    
    // Then
    InOrder inOrder = inOrder(databaseService, emailService);
    inOrder.verify(databaseService).deleteUser(email);
    inOrder.verify(emailService).sendGoodbyeEmail(email);
}

@Test
void shouldHandleEmailServiceFailure() {
    // Given
    String email = "test@example.com";
    String name = "John Doe";
    
    doNothing().when(databaseService).saveUser(any(User.class));
    doThrow(new EmailException("SMTP server unavailable"))
        .when(emailService).sendWelcomeEmail(email, name);
    
    // When & Then
    assertThrows(EmailException.class, () -> {
        userService.createUser(email, name);
    });
    
    verify(databaseService).saveUser(any(User.class));
    verify(emailService).sendWelcomeEmail(email, name);
}

@Test
void shouldCaptureArgumentsInVoidMethod() {
    // Given
    String email = "test@example.com";
    String name = "John Doe";
    ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
    
    // When
    userService.createUser(email, name);
    
    // Then
    verify(databaseService).saveUser(userCaptor.capture());
    User capturedUser = userCaptor.getValue();
    assertEquals(email, capturedUser.getEmail());
    assertEquals(name, capturedUser.getName());
}

Использование doAnswer для сложной логики

Когда нужна более сложная логика, используй doAnswer():

@Test
void shouldExecuteCustomLogicInVoidMethod() {
    // Given
    String email = "test@example.com";
    String name = "John Doe";
    AtomicBoolean emailSent = new AtomicBoolean(false);
    
    doAnswer(invocation -> {
        String emailArg = invocation.getArgument(0);
        String nameArg = invocation.getArgument(1);
        
        // Кастомная логика
        if (emailArg.contains("@")) {
            emailSent.set(true);
            System.out.println("Email sent to: " + emailArg);
        }
        return null;
    }).when(emailService).sendWelcomeEmail(anyString(), anyString());
    
    // When
    userService.createUser(email, name);
    
    // Then
    assertTrue(emailSent.get());
    verify(emailService).sendWelcomeEmail(email, name);
}

Сравнение подходов к тестированию void методов

Подход Преимущества Недостатки Когда использовать
doNothing() Простота, быстродействие Не проверяет логику Методы без побочных эффектов
doThrow() Тестирование исключений Только негативные сценарии Обработка ошибок
doAnswer() Полный контроль логики Сложность, медленнее Сложные сценарии
verify() Проверка вызовов Не контролирует выполнение Проверка взаимодействий

Типичные ошибки и как их избежать

Самые частые проблемы при работе с void методами:

  • Использование when() вместо doAction() — приводит к ошибкам компиляции
  • Забывание verify() — тесты проходят, но не проверяют взаимодействие
  • Неправильный порядок вызовов — используй InOrder для проверки последовательности
  • Игнорирование исключений — всегда тестируй negative cases

Неправильно:

// Не компилируется!
when(emailService.sendWelcomeEmail(anyString(), anyString())).thenReturn(null);

Правильно:

doNothing().when(emailService).sendWelcomeEmail(anyString(), anyString());

Интеграция с другими инструментами

Mockito отлично работает с другими testing framework’ами:

  • Spring Boot Test — используй @MockBean для интеграционных тестов
  • TestContainers — комбинируй моки с реальными базами данных
  • WireMock — мокирование HTTP-сервисов совместно с void методами
  • PowerMock — для статических и финальных методов (deprecated, лучше рефакторить)

Для серверных приложений особенно полезно сочетание Mockito с VPS для CI/CD пайплайнов и выделенными серверами для нагрузочного тестирования.

Автоматизация и скрипты

Создай Maven-профиль для запуска только тестов с моками:

<profile>
    <id>mock-tests</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <includes>
                        <include>**/*MockTest.java</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile>

И bash-скрипт для автоматизации:

#!/bin/bash
echo "Running mock tests..."
mvn test -Pmock-tests

if [ $? -eq 0 ]; then
    echo "✅ All mock tests passed!"
else
    echo "❌ Some tests failed"
    exit 1
fi

Статистика и бенчмарки

По данным JVM Ecosystem Report 2023, Mockito используется в 78% Java-проектов. Тесты с моками выполняются в среднем в 10-15 раз быстрее интеграционных тестов. Покрытие кода с void методами обычно составляет 60-70% от общего покрытия в enterprise-приложениях.

Альтернативы Mockito для void методов:

  • EasyMock — более verbose синтаксис
  • PowerMock — для статических методов (устарел)
  • JMockit — более мощный, но сложнее в изучении
  • Spock — для Groovy/Kotlin проектов

Интересные факты и нестандартные применения

Знал ли ты, что Mockito может мокировать даже финальные классы начиная с версии 2.0? Это открывает возможности для тестирования legacy-кода без рефакторинга.

Необычные способы использования:

  • Тестирование логгирования — мокируй Logger для проверки сообщений
  • Профилирование производительности — используй doAnswer() для измерения времени выполнения
  • Симуляция медленных операций — добавляй Thread.sleep() в doAnswer()
  • Тестирование retry-логики — выбрасывай исключения на первых вызовах

Полезные ссылки:

Заключение и рекомендации

Mockito для void методов — это мощный инструмент, который должен быть в арсенале каждого Java-разработчика. Основные принципы успешного использования:

  • Используй doAction().when() синтаксис для void методов
  • Всегда проверяй взаимодействие с помощью verify()
  • Тестируй как позитивные, так и негативные сценарии
  • Контролируй порядок вызовов с InOrder
  • Не забывай про ArgumentCaptor для сложных проверок

Для production-окружений рекомендую использовать моки в unit-тестах (быстро и изолированно) и интеграционные тесты с реальными сервисами для критичных бизнес-процессов. Это обеспечит баланс между скоростью разработки и надёжностью системы.

Помни: хорошие тесты — это не только высокое покрытие кода, но и понятность того, что именно тестируется. Void методы часто содержат критически важную бизнес-логику, и их правильное тестирование может спасти от серьёзных багов в production.


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

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked