My goal is to migrate a Spring Boot application previously developed with Spring Boot 1.3 to the newest Spring Boot version 1.4. The application consists of several maven modules and only one of them contains class annotated with @SpringBootApplication
.
One part of migration is to use @WebMvcTest
annotation to efficiently test controllers, and here I get an issue.
Consider an example application from Spring Boot github page. @WebMvcTest
annotation works perfectly, because, as far as I understand (after I did several tests), there is a class in the main package annotated with @SpringBootApplication
. Note that I follow the same concept as shown in the example above for my own @WebMvcTest
tests.
The only difference I see that in my application, controller classes are located in a separate maven module (without @SpringBootApplication
annotated class), but with @Configuration and SpringBootConfiguration
configurations. If I do not annotate any class with @SpringBootApplication
I always get an assertion while testing controller. My assertion is the same as when SampleTestApplication class in the example above modified to have only @EnableAutoConfiguration
and @SpringBootConfiguration
annotations (@SpringBootApplication
is not present):
getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests) Time elapsed: 0.013 sec <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)
How should I deal with that? Should I always have class annotated with @SpringBootApplication in order to run @WebMvcTest tests?
EDIT 1: I did a small maven project with 2 modules and a minimal configuration. It is here. Now, I get NoSuchBeanDefinitionException exception for repository defined in another module. If I configure "full" @SpringBootApplication - everything is fine.
EDIT 2: I modified small test project from EDIT 1 to give an original issue. I was playing with different annotations and added @ComponentScan on configuration class, because I suspected that beans are not registered properly. However, I expect that only @Controller bean (defined in @WebMvcTest(...class)) shall be registered based on magic behind @WebMvcTest behaviour.
EDIT 3: Spring Boot project issue.
Short answer: I believe so.
Long answer:
I believe @WebMvcTest
needs to find the SpringBootApplication configuration since WebMvcTest
's sole purpose is to help simplify tests (SpringBootApplication
would rather try to load the whole world).
In your specific case, since you don't have any in your non-test packages, I believe it also finds SampleTestConfiguration which is annotated with @ScanPackages
and somehow loads every beans.
Add the following in src/main/java/sample/test
@SpringBootApplication
public class SampleTestConfiguration {
}
And change your test to this:
@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private MyService ms;
@Autowired
private ApplicationContext context;
@Test
public void getDataAndExpectOkStatus() throws Exception {
given(ms.execute("1")).willReturn(false);
mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
}
@Test
public void testMyControllerInAppCtx() {
assertThat(context.getBean(MyController.class), is(not(nullValue())));
}
@Test
public void testNoMyAnotherControllerInAppCtx() {
try {
context.getBean(MyAnotherController.class);
fail("Bean exists");
} catch (BeansException e) {
// ok
}
}
}
@WebMvcTest
finds the SpringBootApplication
, then load only a limited number of beans (see documentation):
@WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular @Component beans will not be scanned when using this annotation.
WebMvcTest
requires SpringBootApplication
: WebMvcTest
inherits many AutoConfiguration, so it needs SpringBoot to load them. Then it disables many other AutoConfiguration and your Controllers become easily testable.
The whole point of using WebMvcTest
is when you have a SpringBootApplication
and you wish to make it simpler to test by disabling all beans except Controllers. If you don't have SpringBootApplication, then why use WebMvcTest at all?
It's an old topic, but there is a solution which wasn't mentioned here.
You can create a class annotated with SpringBootApplication
just in your test sources. Then, you still have a nice, multi-module structure of your project, with just one "real" SpringBootApplication
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With