1. Обзор
В этом руководстве мы обсудим, как использовать внедрение зависимостей для вставки макетов Mockito в Spring Beans для модульного тестирования.
В реальных приложениях, где компоненты часто зависят от доступа к внешним системам, важно обеспечить надлежащую изоляцию тестов, чтобы мы могли сосредоточиться на тестировании функциональности данного модуля без необходимости задействовать всю иерархию классов для каждого теста.
Внедрение макета — это чистый способ ввести такую изоляцию.
2. Зависимости Maven
Нам нужны следующие зависимости Maven для модульных тестов и фиктивных объектов:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
</dependency>
Мы решили использовать Spring Boot для этого примера, но классический Spring тоже подойдет.
3. Написание теста
3.1. Бизнес-логика
Во-первых, давайте создадим простой сервис, который мы будем тестировать:
@Service
public class NameService {
public String getUserName(String id) {
return "Real user name";
}
}
Затем мы внедрим его в класс UserService
:
@Service
public class UserService {
private NameService nameService;
@Autowired
public UserService(NameService nameService) {
this.nameService = nameService;
}
public String getUserName(String id) {
return nameService.getUserName(id);
}
}
В этой статье указанные классы возвращают одно имя независимо от предоставленного идентификатора. Это сделано для того, чтобы мы не отвлекались на тестирование какой-либо сложной логики.
Нам также понадобится стандартный основной класс Spring Boot для сканирования bean-компонентов и инициализации приложения:
@SpringBootApplication
public class MocksApplication {
public static void main(String[] args) {
SpringApplication.run(MocksApplication.class, args);
}
}
3.2. Тесты
Теперь перейдем к тестовой логике. Прежде всего, мы должны настроить контекст приложения для тестов:
@Profile("test")
@Configuration
public class NameServiceTestConfiguration {
@Bean
@Primary
public NameService nameService() {
return Mockito.mock(NameService.class);
}
}
Аннотация @Profile
указывает Spring применять эту конфигурацию только тогда, когда активен «тестовый» профиль. Аннотация @Primary
предназначена для того, чтобы убедиться, что этот экземпляр используется вместо реального для автоматического подключения. Сам метод создает и возвращает макет Mockito нашего класса NameService
.
Теперь мы можем написать модульный тест:
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MocksApplication.class)
public class UserServiceUnitTest {
@Autowired
private UserService userService;
@Autowired
private NameService nameService;
@Test
public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
Mockito.when(nameService.getUserName("SomeId")).thenReturn("Mock user name");
String testName = userService.getUserName("SomeId");
Assert.assertEquals("Mock user name", testName);
}
}
Мы используем аннотацию @ActiveProfiles
, чтобы включить «тестовый» профиль и активировать фиктивную конфигурацию, которую мы написали ранее. В результате Spring автоматически подключает реальный экземпляр класса UserService
, но макет класса NameService
. Сам тест является довольно типичным тестом JUnit+Mockito. Мы настраиваем желаемое поведение макета, затем вызываем метод, который хотим протестировать, и утверждаем, что он возвращает ожидаемое значение.
Также возможно (хотя и не рекомендуется) избегать использования профилей среды в таких тестах. Для этого мы удаляем аннотации @Profile
и @ActiveProfiles
и добавляем аннотацию @ContextConfiguration(classes = NameServiceTestConfiguration.class)
в класс UserServiceTest
.
4. Вывод
В этой краткой статье мы узнали, как легко внедрить моки Mockito в Spring Beans.
Как обычно, все образцы кода доступны на GitHub .