#java #spring #junit #dependency-injection #mockito
Вопрос:
У меня есть одноэлементный класс (так называемый частный конструктор), который должен использовать хранилище данных Spring во время инициализации. У меня есть один, введенный в качестве аргумента конструктора. Грубо:
@Controller
public class MyClass {
@Autowired
private MyClass(MyRepository repo) {
repo.findAll();
}
}
Я хочу модульно протестировать свой класс, поэтому мне нужно, чтобы макет репозитория был инициализирован с помощью макетных значений, а затем передан в мой класс до инициализации моего класса. Как мне написать свои насмешки над Mockito в тесте JUnit, чтобы это стало возможным?
Ответ №1:
Вам не нужна пружина; это преимущество инъекции конструктора. Просто используйте MyRepository mockRepo = mock(MyRepository.class)
и new MyClass(mockRepo)
.
(Кстати, ваш конструктор должен быть общедоступным. Похоже, вы совершаете распространенную ошибку, путаете разные значения слова «синглтон»; в случае DI это просто означает, что контейнер создает только один экземпляр и разделяет его. Наконец, если у вас есть только один конструктор, вам он не нужен @Autowired
.)
Комментарии:
1. При использовании с реальными данными мой класс займет немного времени для инициализации и будет использовать нетривиальный объем памяти, поэтому я хочу явно гарантировать, что когда-либо будет только один экземпляр моего класса.
2. @KonstantinTarashchanskiy Это существенный запах кода, и тем более причина для очистки вашей структуры; например, в тесте вы должны иметь возможность возвращать несколько сгенерированных элементов (а также ноль элементов!),
findAll
чтобы всесторонне протестировать поведение.3. Я согласен с Крилис-осторожно оптимистично — сделать конструктор в классе общедоступным и установить область действия компонента весной как одноэлементную. Это сделало бы это тривиальным для тестирования и по-прежнему поддерживало бы один экземпляр в контексте Spring, что довольно часто означает один экземпляр на JVM. Если по какой-либо причине вы абсолютно не можете этого сделать, полностью удалите модификатор доступа (используйте доступ по умолчанию / пакет) и аннотируйте конструктор с помощью @VisibleForTesting. Ваш тест по-прежнему остается тривиальным, и никакие классы за пределами пакета, содержащего контроллер, не смогут вызвать конструктор.
Ответ №2:
Модульный тест должен быть независимым. Это означает, что мы не используем реальные данные из базы данных, даже если вызываем какую-либо службу из нашего тестового файла.
Предполагая, что вы используете JUni5 и имеете findAllStudents()
в своем контроллере. Итак, ваш тестовый файл примерно такой
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyClassTest {
@Mock
private MyRepository repo;
@InjectMocks
private MyClass controller;
@BeforeAll
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testDataIsExist() {
List<String> expectednames = new ArrayList();
expectedNames.add("Foo");
expectedNames.add("Bar");
Mockito.when(myRepo.findAll()).thenReturn(expectedNames);
List<String> result = controller.findAllStudents();
Assertions.assertNotNull(result);
Assertions.assertEquals(expectednames, result);
}
}
Таким образом, мы издеваемся над всеми службами, которые мы используем в контроллере, а затем вводим их в сам контроллер. Затем в методе тестирования мы имитируем repo.findAll()
возврат expectedNames
, поэтому, если контроллер найдет эту функцию, он вернет то, что говорит вернуть мок.
После вызова функции мы должны убедиться, что результат соответствует нашим ожиданиям.
Комментарии:
1. Это действительно помогает ввести мои насмешки в мой контроллер, но у меня нет возможности инициализировать мои насмешки до их введения, поэтому конструктор контроллера не видит макетные данные.
Ответ №3:
Для реализации «модульный тест» имеет мало значения @Controller
. Если вы используете @WebMvcTest
, то вы можете использовать @MockBean
:
@WebMvcTest
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyRepository repository;
@Test
void testSomething() {
mockMvc.perform( ... );
}
}
Комментарии:
1. Почему бы мне не захотеть провести модульное тестирование своих контроллеров? В них у меня есть нетривиальная бизнес-логика. В традиционных моделях MVC большая часть логики должна находиться на уровне контроллера.
2. Потому что у контроллеров обычно много аннотаций, в которых вы также хотите их протестировать. Это проще всего использовать
@WebMvcTest
. Обычно контроллеры не должны иметь в себе бизнес-логики. Они должны иметь дело только с преобразованием HTTP. Я размещаю бизнес-логику на уровне сервисов.