Does #WebMvcTest require #SpringBootApplication annotation? - spring-boot

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.

Yes,according to the spring boot docs
The search algorithm works up from the package that contains the test until it finds a #SpringBootApplication or #SpringBootConfiguration annotated class. As long as you’ve structure your code in a sensible way your main configuration is usually found.
But after I started using #WebMvcTest,spring boot still try to load other beans, finally TypeExcludeFilter did the trick.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = {JzYsController.class} )
public class JzYsControllerTest {
private static final String REST_V4_JZYS = "/rest/v4/JzYs";
#Autowired
private MockMvc mockMvc;
#MockBean
private JzYsService service;
#Test
public void deleteYsByMlbh() throws Exception {
Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
.andExpect(status().isNoContent());
}
#SpringBootConfiguration
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public static class config{
}
}

There is one more solution. You can not use #WebMvcTest, but configure MockMvc yourself through the builder
class TestControllerTest {
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders.standaloneSetup(new TestController())
.build();
}
#Test
void test() throws Exception {
// When
var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));
// Then
res.andExpect(status().isOk());
}
}
But this solution may entail a number of other problems, such as problems with configurations, environment property injections, etc.

Related

PostConstruct and test

I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.

How to disable #Configuration initialization in WebFluxTest?

I would like to write tests for reactive controller using #WebFluxTest annotation, mocking all dependencies.
#WebFluxTest(controllers = MyController.class)
public class MyControllerTest {
#MockBean
SomeService service;
#Autowired
WebTestClient webClient;
//some tests
}
From what I understand, the WebFluxTest annotation shall apply only configuration relevant to WebFlux tests (i.e. #Controller, #ControllerAdvice, etc.), but not another beans.
My spring boot app contains a number of #Configuration classes that configure a number of beans (annotated as #Bean). Some of those configurations have also dependencies (autowired by constructor).
#Configuration
#RequiredArgsConstructor
public class MyConfig {
private final AnotherConfig anotherConfig;
#Bean
//...
}
When I run my web flux tests, I can see the context initialization contains an attempt to initialize the MyConfig (and it fails because of the missing dependency which comes from 3rd party auto-configured lib). How can I configure the test to skip initialization of all of these?
I am able to exclude the problematic configuration class only by excluding auto configuration of the whole app.
#WebFluxTest(controllers = MyController.class, excludeAutoConfiguration = {MyApplication.class})
public class MyControllerTest { ... }
where MyApplication is the spring boot app autoscanning those configuration classes.
But how can I achieve to skip initialization of MyConfig only? Or even better, how can I achieve to only include a list of configurations to be initialized?
Add
#ActiveProfiles("YOUR_ENV_OTHER_THAN_TEST")
below or above #Configuration
For multiple environments..
#ActiveProfiles(profiles ={env1, env2,env3})

How Do I Manually Wire A Spring Boot Integration Test?

Normally, I would test the web layer in a Spring project like this:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
#LocalServerPort
int randomServerPort;
#Autowired
private TestRestTemplate restTemplate;
However, I currently have a difficult back end that requires a specific #TestConfiguration class to manually instantiate the test dependencies using beans.
This ultimately means that I can't use the #SpringBootTest annotation as it will try to create conflicting beans and fail to instantiate others.
If I am not using the #SpringBootTest annotation, I can manually create the TestRestTemplate instead of autowiring it, but what do I need to do to start the embedded local server on a random port?
I would still continue using #SpringBootTest, and combine that with using #Profile on your configuration classes.
That way you could have a configuration which is only used during tests, by using #ActiveProfiles on your #SpringBootTest classes. In the same way you can turn other config classes on or off depending on whether you want them to load or not.
For example on your test would have the following
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles("unittest")
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
...
}
Then create a configuration class which will instantiate your components the way you want them in test
#Profile("unittest")
#Configuration
public void TestConfiguration {
...
}
And you can use profiles to stop your other configuration class from loading during tests.
#Profile("!unittest")
#Configuration
public void ProdConfiguration {
...
}

#webMvcTest is not excluding and loading beans marked as #Repository

I've a #RestController which has only one dependency in field #Autowire
that dependency is #component, that component Class definition has some autowired fields which are #service and those services have some #repositories.
In this whole flow I've kafka, Quartz, Cassandra and DB2
So when I was creating a Unit test case for my controller, I dont want to setup whole application. so I decided to use #webMvcTest and used #MockBean on my only one dependency of controller class.
But my Test is throwing and exception because its trying to create a Dao bean, which is marked as #repository.
#ActiveProfiles("test")
#WebMvcTest(controllers = MyControllerTest .class)
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
assert(true);
}
}
Here is oversimplified version of code
#Component
class MyControllerDependency {
#AutoiWired
MyCustomService service;
}
#Service
class MyCustomService{
#Autowired
MyCustomDao dao;
}
#Repository
class MyCustomDao{
#Autowired
private JdbcTemplate template;
}
I'm getting following exception in test.
Exception
***************************
APPLICATION FAILED TO START
***************************
Description:
Field template in com.....MyCustomDao` required a bean of type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Question is, When I'm using #WebMvcTest slice and already mocking the only required dependency MyControllerDependency then why spring test context is trying to load MyCustomDao which is annotated as #Repository.
I can do integration testing with SpringbootTest & AutoconfigureMockMVC, but for writing Junit test just for controller, I need to use WebMvcTest slice. which is creating a problem.
I ran into a similar problem where I want to test only my controller using #WebMvcTest, but the spring context was trying to create nondependent spring beans and was failing as below.
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'TestController' defined in file ...
Solution: load only the controller your testing for an example like #ContextConfiguration(classes = DemoController.class).
Also, find below a complete sample
#WebMvcTest
#ContextConfiguration(classes = DemoController.class)
public class DemoControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
DemoService demoService;
#Test
public void testGetAllProductCodes_withOutData() throws Exception {
when(productCodeService.getAllProductCodes()).thenReturn(new ArrayList<ProductCodes>());
mockMvc.perform(MockMvcRequestBuilders.get("/services/productCodes")).andExpect(MockMvcResultMatchers.status().isNoContent());
}
}
}
Do you have any #ComponentScan("...") annotation active on your #SpringBootApplication?
As described in Spring Boot Reference Documentation:
Another source of confusion is classpath scanning. Assume that, while you structured your code in a sensible way, you need to scan an additional package. Your application may resemble the following code:
#SpringBootApplication
#ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. For instance, a #DataJpaTest seems to suddenly scan components and user configurations of your application. Again, moving the custom directive to a separate class is a good way to fix this issue.
One solution is to create a seperate #Configuration that is annotated with #ComponentScan. When creating a #WebMvcTest the configuration (and its component scan is ignored).
#Configuration
#ComponentScan("com.example.another")
public class DbConfig {
}
This usually happens when you have explicit #ComponentScan annotation on the spring boot main application class.
The #ComponentScan annotation suppresses the default component scanning mechanism that happens with #Webmvctest where it scans up the package hierarchy and apply excludeFilters to find only controller and its related classes.
When you mock your bean using #MockBean annotation you should define what that mocked bean should do when you call it's method, you usually do this using when in Mockito. In your case it can be done this way:
#ActiveProfiles("test")
#WebMvcTest
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
when(dependency.sample()).thenReturn("Hello, Mock");
mockMvc.perform(get("/api/test/restpoint")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
}
}
At this line:
when(dependency.sample()).thenReturn("Hello, Mock");
Instead of dependency.sample() you should put whatever method of MyControllerDependency class your controller call when you send a GET request to /api/test/restpoint path and with thenReturn("Hello, Mock") you define what is the mocked output of that method (when it gets called by your controller in your Unit test).

How to exclude/disable a specific auto-configuration in Spring boot 1.4.0 for #DataJpaTest?

I am using the #DataJpaTest from Spring for my test which will then use H2 as in memory database as described here . I'm also using Flyway for production. However once the test starts FLyway kicks in and reads the SQL file. How can I exclude the FlywayAutoConfiguration and keep the rest as described here in spring documentation in order to let Hibernate create the tables in H2 for me?
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private MyRepository triggerRepository;
}
Have you tried the #OverrideAutoConfiguration annotation?
It says it "can be used to override #EnableAutoConfiguration".
I'm assuming that from there you can somehow exclude FlywayAutoConfiguration
like so:
#EnableAutoConfiguration(exclude=FlywayAutoConfiguration.class)
Adding the dependency on an in-memory database to my build.gradle
e.g. testRuntime "com.h2database:h2:1.4.194"
And adding flyway.enabled=false to application.properties in src/test/resources worked for me.
I am converting an old JDBC app into a spring-data-jpa app and I'm working on the first tests now. I kept seeing a security module instantiation error from spring-boot as it tried to bootstrap the security setup, even though #DataJpaTest should theoretically be excluding it.
My problem with the security module probably stems from the pre-existing implementation which I inherited using PropertySourcesPlaceholderConfigurer (via my PropertySpringConfig import below)
Following the docs here:
http://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#test-auto-configuration
and your comments on #LiviaMorunianu's answer, I managed to work my way past every spring-boot exception and get JUnit to run with an auto-configured embedded DB.
My main/production spring-boot bootstrap class bootstraps everything including the stuff I want to exclude from my tests. So instead of using #DataJpaTest, I copied much of what it is doing, using #Import to bring in the centralized configurations that every test / live setup will use.
I also had issues because of the package structure I use, since initially I was running the test which was based in com.mycompany.repositories and it didn't find the entities in com.mycompany.entities.
Below are the relevant classes.
JUnit Test
#RunWith(SpringRunner.class)
#Transactional
#Import({TestConfiguration.class, LiveConfiguration.class})
public class ForecastRepositoryTests {
#Autowired
ForecastRepository repository;
Forecast forecast;
#Before
public void setUp() {
forecast = createDummyForecast(TEST_NAME, 12345L);
}
#Test
public void testFindSavedForecastById() {
forecast = repository.save(forecast);
assertThat(repository.findOne(forecast.getId()), is(forecast));
}
Live Configuration
#Configuration
#EnableJpaRepositories(basePackages = {"com.mycompany.repository"})
#EntityScan(basePackages = {"com.mycompany.entity"})
#Import({PropertySpringConfig.class})
public class LiveConfiguration {}
Test Configuration
#OverrideAutoConfiguration(enabled = false)
#ImportAutoConfiguration(value = {
CacheAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
TransactionAutoConfiguration.class,
TestDatabaseAutoConfiguration.class,
TestEntityManagerAutoConfiguration.class })
public class TestConfiguration {
// lots of bean definitions...
}
PropertySpringConfig
#Configuration
public class PropertySpringConfig {
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
throws IOException {
return new CorePropertySourcesPlaceholderConfigurer(
System.getProperties());
}
}
In my particular case, i needed to disable the FlywayDB on in-memory integration tests. These are using a set of spring annotations for auto-configuring a limited applicationContext.
#ImportAutoConfiguration(value = TestConfig.class, exclude = FlywayAutoConfiguration.class)
the exclude could effectively further limit the set of beans initiated for this test
I had the same problem with my DbUnit tests defined in Spock test classes. In my case I was able to disable the Flyway migration and managed to initialize the H2 test database tables like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = ["flyway.enabled=false", "spring.datasource.schema=db/migration/h2/V1__init.sql"])
I added this annotation to my Spock test specification class. Also, I was only able to make it work if I also added the context configuration annotation:
#ContextConfiguration(classes = MyApplication.class)
I resolved the same issue by excluding the autoconfiguration from my application definition, i.e.
#SpringBootApplication(exclude = {FlywayAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
you can also sue the following annotation:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {MySqlConfiguration.class, ...})
public class TheClassYouAreUnitTesting {
}
You can just disable it in your test yaml file:
flyway.enabled: false

Resources