#java #unit-testing #junit #spring-boot #spring-test
#java #модульное тестирование #junit #spring-boot #spring-test
Вопрос:
У меня есть проходящий модульный тест, но я не знаю, почему он работает. Установка точек отладки в IntelliJ показывает, что свойство Autowired имеет значение null повсюду. Я опубликовал проект на GitHub на случай, если кто-то захочет взглянуть: https://github.com/leonj1/spring_spark
Ниже приведен код, в котором я прокомментировал точки отладки (например #1, #2, #3). Когда попадает точка отладки # 1 # 2, значение @Autowired PersonRoute personRoute
всегда равно нулю. Вероятно, это связано с тем, что внутренний статический класс создается перед @ContextConfiguration
синтаксическим @Configuration
анализом.
Но тогда как testServer
@ClassRule
получить регидратацию SimpleController
? Я знаю, что Spring использует отражения для проверки и установки свойств, но для меня было бы новостью, если бы он также делал это для объектов, которые уже созданы.
Я пытаюсь понять это, потому что на данный момент незнание заставляет меня меньше доверять тесту.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DbUnitTestExecutionListener.class
})
@DatabaseSetup("persons-entities.xml")
@Transactional
public class SimpleControllerTest {
@Configuration
@ComponentScan(basePackages = {"com.jose.sandbox"})
static class SomeConfig {
// because @PropertySource doesn't work in annotation only land
@Bean
public PropertyPlaceholderConfigurer propConfig() {
// #3 debug point 3, then shows Spring creating beans
// but how does SimpleController.class get the property set?
// does this mean 2 different SimpleController objects exist in the JVM, but only one is "wired"?
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("application.properties"));
return ppc;
}
}
@Component
public static class TestControllerTestApplication implements SparkApplication {
// #1 debug point 1 in IDE shows this as NULL
@Autowired PersonRoute personRoute;
@Override
public void init() {
new SimpleController(this.personRoute);
}
}
@ClassRule
public static SparkServer<SimpleControllerTest.TestControllerTestApplication> testServer
= new SparkServer<>(SimpleControllerTest.TestControllerTestApplication.class, 4567);
@Test
public void verifyGetAllPeople() throws Exception {
// given
String payload = null;
// when
SparkClient.UrlResponse response = testServer.getClient().doMethod("GET", "/people/count", payload);
// then
int expected = 3; // because that's how many exist in persons-entities.xml
assertEquals(200, response.status);
assertEquals(expected, Integer.parseInt(response.body));
assertNotNull(testServer.getApplication());
}
@Mock
Response response;
}
Вот контроллер, начальная точка отладки которого показывает NULL для Autowired
свойства. Точка отладки никогда не повторяется, так как же это устанавливается?
@Component
public class SimpleController {
// #2 debug point 2, also shows this as NULL
@Autowired PersonRoute personRoute;
public SimpleController() {}
public SimpleController(PersonRoute personRoute) {
this.personRoute = personRoute;
}
@PostConstruct
public void init() {
get("/people/count", this.personRoute);
}
}
Любая помощь была бы признательна, поскольку сложно построить этот тест, пока он не понят.
Комментарии:
1. Кстати, это не модульный тест. Это интеграционный тест.
2. Кроме того,
PropertyPlaceholderConfigurer
должно бытьstatic
. Подробности см. в Javadoc@Bean
.3. И… наличие
@Transactional
в вашем примере бессмысленно, поскольку вы не регистрируетеTransactionalTestExecutionListener
. Рассмотрите возможность только объявления прослушивателя DBUnit и использованияMERGE_WITH_DEFAULTS
в качествеmergeMode
with@TestExecutionListeners(...)
.
Ответ №1:
Но тогда как
testServer
@ClassRule
получить регидратациюSimpleController
?
Это не так.
Фактически, вы можете закомментировать весь следующий код в своем тесте следующим образом, и тест все равно пройдет.
// @Component
public static class TestControllerTestApplication implements SparkApplication {
// #1 debug point 1 in IDE shows this as NULL
// @Autowired PersonRoute personRoute;
@Override
public void init() {
// new SimpleController(this.personRoute);
}
}
Вот контроллер, начальная точка отладки которого показывает NULL для
Autowired
свойства. Точка отладки никогда не повторяется, так как же это устанавливается?
Spring вводит @Autowired PersonRoute
в SimpleController
использование отражения.
В принципе, происходит следующее.
- Вы поручаете Spring выполнить сканирование компонентов, начиная с
com.jose.sandbox
базового пакета, что приводит к полной инициализацииPersonRepository
,PersonRoute
, иSimpleController
beans . PersonRoute
Компонент вводится вSimpleController
компонент.init()
МетодSimpleController
компонента вызывается Spring, поскольку вы аннотировали его@PostConstruct
, и он регистрирует маршрут с помощью Spark посредствомget(...)
вызова статического метода.- Затем платформа Spark Test создает ваш экземпляр
TestControllerTestApplication
на этапе «до класса» для правила JUnit 4, но маршрут уже зарегистрирован. Таким образом, вам не нужноTestControllerTestApplication
ничего делать. Вот почему я прокомментировал приведенный выше код.
Другими словами, вам нужна только пустая TestControllerTestApplication
реализация, потому SparkServer
что этого требует платформа тестирования Spark. Без Spring это имело бы смысл реализовать, но из-за того, как вы настроили вещи с помощью Spring, нет необходимости его реализовывать.
Надеюсь, это прояснит ситуацию для вас.
С уважением,
Сэм (автор Spring TestContext Framework)