简体   繁体   中英

Not sure why this unit test works. Spring ConfigurationContext updating or creating new object?

I have a passing unit test, but I don't know why its working. Setting debug points in IntelliJ shows that the Autowired property is null throughout. I've published the project on GitHub in case someone wants to take a look: https://github.com/leonj1/spring_spark

Below is the code where I have commented debug points (eg #1, #2, #3). When debug point #1 + #2 get hit, the @Autowired PersonRoute personRoute is always null. This is probably because the inner static class gets instantiated ahead of @ContextConfiguration parsing @Configuration .

But then how does testServer in @ClassRule get the rehydrated SimpleController ? I know Spring uses Reflections to inspect and set properties, but it would be news to me if it also does that for objects which are already instantiated.

I'm trying to understand this because at the moment not knowing makes me have less confidence in the test.

@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;
}

Here is the Controller which initial debug point shows NULL for the Autowired property. The debug point never gets hit again, so how is this being set?

@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);
    }
}

Any help would be appreciated since it difficult to build on this test until its understood.

But then how does testServer in @ClassRule get the rehydrated SimpleController ?

It doesn't.

In fact, you can comment out all of the following code in your test as follows, and the test still passes.

// @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);
    }
}

Here is the Controller which initial debug point shows NULL for the Autowired property. The debug point never gets hit again, so how is this being set?

Spring injects the @Autowired PersonRoute into the SimpleController using reflection.

Basically, what's happening is the following.

  1. You instruct Spring to perform component scanning beginning with the com.jose.sandbox base package which results in fully initialized PersonRepository , PersonRoute , and SimpleController beans.
  2. The PersonRoute bean gets injected into the SimpleController bean.
  3. The init() method of the SimpleController bean gets invoked by Spring since you annotated it with @PostConstruct , and that registers the route with Spark via the static get(...) method invocation.
  4. The Spark Test framework then instantiates your TestControllerTestApplication during the "before class" phase for the JUnit 4 Rule, but the route has already been registered. Thus there is no need for your TestControllerTestApplication to actually do anything. That's why I commented out the code above.

In other words, you only need the empty TestControllerTestApplication implementation because the SparkServer from the Spark Test framework requires it. Without Spring it would make sense to implement that, but due to the way you have configured things with Spring there is no need to implement it.

Hope this clarifies things for you.

Regards,

Sam ( author of the Spring TestContext Framework )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM