Не уверен, почему работает этот модульный тест. Spring ConfigurationContext обновляет или создает новый объект?

#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 использование отражения.

В принципе, происходит следующее.

  1. Вы поручаете Spring выполнить сканирование компонентов, начиная с com.jose.sandbox базового пакета, что приводит к полной инициализации PersonRepository , PersonRoute , и SimpleController beans .
  2. PersonRoute Компонент вводится в SimpleController компонент.
  3. init() Метод SimpleController компонента вызывается Spring, поскольку вы аннотировали его @PostConstruct , и он регистрирует маршрут с помощью Spark посредством get(...) вызова статического метода.
  4. Затем платформа Spark Test создает ваш экземпляр TestControllerTestApplication на этапе «до класса» для правила JUnit 4, но маршрут уже зарегистрирован. Таким образом, вам не нужно TestControllerTestApplication ничего делать. Вот почему я прокомментировал приведенный выше код.

Другими словами, вам нужна только пустая TestControllerTestApplication реализация, потому SparkServer что этого требует платформа тестирования Spark. Без Spring это имело бы смысл реализовать, но из-за того, как вы настроили вещи с помощью Spring, нет необходимости его реализовывать.

Надеюсь, это прояснит ситуацию для вас.

С уважением,

Сэм (автор Spring TestContext Framework)