BARHOUMI
BARHOUMI

Reputation: 214

Spring Boot unit test constructor injection

I'm using Spring Boot to create a REST API and write some unit tests on my controllers. I know that the recommended manner to inject beans in spring is the constructor injection. But when i add the @SpringBootTest annotation to my test class, I can not inject my controller class with constructor, I find myself obliged to use @Autowired.

Have some explanation and is there another way to use constructor injection with SpringBootTest.

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private PersonController controller;

    @Autowired
    private TestRestTemplate restTemplate;


    @Test
    public void greetingShouldReturnDefaultMessage() throws Exception {
        assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/cvtech/Persons/",
                                                  String.class)).contains("content");
    }

    @Test
    public void contextLoads() throws Exception {
        assertThat(controller).isNotNull();
    }
    @Test
    void findAllByJob() {
    }
}

Upvotes: 2

Views: 6504

Answers (2)

dstibbe
dstibbe

Reputation: 1697

For those using Kotlin, using field-injection means having to use lateinit var fields. Which is far from ideal.

It is possible to use constructor injection on SpringBoot tests however, using the @TestConstructor:

@ExtendWith(SpringExtension::class)
@TestConstructor(autowireMode = ALL)
@SpringBootTest(
    classes = [MyApplication::class],
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
internal class MyIntegrationTest(
    val beanA: BeanA,
    @Qualifier("some qualified") val beanB: BeanB,
) {
...
// Tests can use the beans injected in the constructor without any problems
...
}

Upvotes: 2

Nico Van Belle
Nico Van Belle

Reputation: 5166

It's fine for your test to use field injection as the Test itself is not part of your domain; the test won't be part of your application context.

Also

You don't want to use SpringBootTest to test a controller, because that will wire ALL beans which can be way too heavy and time-consuming. Instead, you probably only want to create your controller and it's dependencies.

So your best option is to use @WebMvcTest which will only create the beans required for testing the specified controller.

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = PersonController.class)
class PersonControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void greetingShouldReturnDefaultMessage() throws Exception {
        mockMvc.perform(get("/cvtech/Persons"))
               .andExpect(status().isOk())
               .andExpect(content().string(contains("content")));
    }
}

Note that @WebMvcTest will search for a class annotated with @SpringBootConfiguration as it's default configuration. If it does not find it, or you want to manually specify some configuration classes, also annotate the test with @ContextConfiguration.

Also, as a sidenote, when using TestRestTemplate, you don't need to specify host and port. Just call restTemplate.getForObject("/cvtech/persons", String.class)); Same when using MockMvc or WebTestClient.

Upvotes: 2

Related Questions