Unable to find a SpringBootConfiguration in Spring Boot Test 1.4 - spring

I'm not able to run a simple test in spring boot 1.4. I followed the tutorial from the official site testing-the-spring-mvc-slice but I didn't get it to work.
every time i get the following error:
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
any ideas, hints?
Thanks in advance
Edit:
this is the controller
#Controller
public class UserManagementController {
#GetMapping(value = "/gs/users/getUsers")
public #ResponseBody String getAllUsers() {
return "test";
}
}
this is the test
#RunWith(SpringRunner.class)
#WebMvcTest(UserManagementController.class)
public class UserManagementControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void showUserView() throws Exception {
this.mvc.perform(get("/gs/users/getUsers"))
.andExpect(status().isOk())
.andDo(print());
}
}
From my point of view it's exactly the same like this post from the site.
the #WebMvcTest will do:
Auto-configure Spring MVC, Jackson, Gson, Message converters etc.
Load relevant components (#Controller, #RestController, #JsonComponent etc)
Configure MockMVC
now why i need to configure a "super" class

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.
So you have annotated your test with #*Test. It run, checked for configuration in subclasses, haven't found any, thrown an exception.
You have to have a config in a package or subpackage of test class or directly pass config class to #ContextConfiguration or #SpringBootTest or have class annotated with #SpringBootApplication.
According to #SpringBootApplication. I have tested controller in way you have mentioned with #WebMvcTest: it works if application has class annotated as #SpringBootApplication and fails with exception you've mentioned if not. There is remark it the article you mentioned:
In this example, we’ve omitted classes which means that the test will
first attempt to load #Configuration from any inner-classes, and if
that fails, it will search for your primary #SpringBootApplication
class.
Github discussion about the same point.
Spring Boot Documentation

Related

#WebMvcTest needs #Import(SecurityConfig.class) to enable SpringSecurity

First of all, its important to specify that the app is already deployed and our spring security configuration works. If an unauthenticated user tries to access to any endpoint of the API it returns 401 Unauthorized.
Following, this sample example, I want to test a Controller with #WebMvcTest and spring security
#WebMvcTest(EmployeeController.class)
#Import(SecurityConfig.class)
class EmployeeControllerTest {
#Autowired
private WebApplicationContext ctx;
protected MockMvc mvc;
#MockBean
private EmployeeService service;
#BeforeEach
public void setUp() {
this.mvc = MockMvcBuilders
.webAppContextSetup(ctx)
.apply(springSecurity())
.build();
}
#Test
void unauthorized_role_should_return_401() {
mvc.perform(get("/employees/1").accept(MediaType.APPLICATION_JSON)
.with(SecurityMockMvcRequestPostProcessors.user("manager").roles("UNKNOWN")))
.andExpect(status().isUnauthorized())
}
}
This code works, but I don't understand why I need to import the SecurityConfig class to the test class. Indeed, if I remove the #Import, mockMvc returns 200. But, every sample project that I found over Github simply use #WebMvcTest even if the project has a SecurityConfig class
There is no need to configure extra thing in order to enable spring security when using #WebMvcTest as #WebMvcTest will enable it automatically.
But if you customise the spring-security by creating your own beans , you still have to define these customised beans for the tests for the customisation to work. #Import is one of the way to define these beans. By consolidating their configuration into a SecurityConfig , it can keep your test cases DRY because if a test case need to test with the security stuff , it just need to import this SecurityConfig rather repeatedly do the same configuration for each test.
So it is rather a code design decision which is an application specific and hence it is normal that not every project will have the same kind of setup.

how to correctly modularize app configuration, so that tests(IT,datajpa,...) does not pick up everything intended for production

In our app I found out, that my integration tests picks up more stuff than I'd like. I'd like to know, how correctly structured app configuration looks like, what do you use, so that I can #Import in tests only those configuration which are used in production, which are needed.
I believe relevant page in documentation is:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-user-configuration
... it's stressed there, that it's important to structure code in sensible way, however it's not shown that much, what that is/means. I know about profiles and can probably create profile which would be unmatched in tests and import manually, but that's probably not that sensible way they were talking about.
Consider this main entrypoint:
#SpringBootApplication
public class DemoApplication {
private final SomeService someService;
public DemoApplication(SomeService someService) {
this.someService = someService;
}
#EventListener(ApplicationReadyEvent.class)
public void started() {
System.out.println(someService.doIt());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
interface of some service:
public interface SomeService {
public String doIt();
}
and configuration:
#Configuration
public class Config {
#Bean
public SomeService createSomeServiceBean() {
return new SomeService() {
#Override
public String doIt() {
return String.format("Hi! (At %s)", LocalDateTime.now());
}
};
}
}
When invoked, entrypoint annotated by #SpringBootApplication will do component scan, will discover configuration and it will work. Reading further in documentation we will find sentence: Test slices exclude #Configuration classes from scanning([if #ComponentScan does have default value of basePackages and basePackagesClasses]), however following test:
#SpringBootTest
class DemoApplicationTests {
#Autowired
private SomeService someService;
#Test
void contextLoads() {
System.out.println(someService.doIt());
}
}
just happily discovers SomeService bean defined. Or did that sentence meant just that tests annotated by for example #DataJpaTest won't register some configurations? Kinda unclear to me, but it does not seem possible, since how would #DataJpaTest would know, which configurations to ommit and which not.
Again, I know how to use profiles/excluding configurations. I'm asking about "sensible way of structuring app".
How to sensibly structure you app and how to configure it so that:
#SpringBootApplication annotated entrypoint will do component scan, find and use configurations, for production, but these configurations needs to be manually imported in tests?
some packages will be automatically scanned for configurations which will be used both in development and tests environments.
The Spring Boot Test support provides annotations that allow to only create a Spring Context with the relevant beans to testing a specific slice of your application.
There is no specific package structure or naming strategy required to make use of this feature.
Here are some of these:
#DataJpaTest: You get a Spring Context with relevant beans to test your JPA <-> Database interface: EntityManager, DataSource, all your interfaces extending JpaRepository
#WebMvcTest: You get a Spring Context with a mocked servlet environment for testing your web layer that includes the following beans for your: all your controller, controller advice, WebMvcConfigurer , Filter, etc. but not anything that is annotated with e.g. #Service or #Component
#SpringBootTest: This will give you a full Spring Context and tries to create all beans for you. You can exclude some autoconfiguration e.g. if you don't want autoconfiguration to kick in:
Example:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#TestPropertySource(properties=
{"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"})
There are way more test slice annotations, you can have a look at here
So these annotations are smart in a way that they know which beans they should include in the context and which to exclude.
A general approach to testing your application can be to use the first two test annotations stated above to verify web and data layer in isolation. Next use Mockito and plain JUnit 5 to unit test your service classes. And finally, write some integration test that creates the whole Spring Context with #SpringBootTest to test everything together.

MockBean and MyBatis Mapper not working together (as they did before Spring Boot 2.2.7)

I am using MyBatis Spring Boot Starter version 2.1.3. Ever since moving to Spring Boot 2.2.7 (I've also tried 2.2.8 and 2.3.1 with the same results), I've had a problem using the MockBean annotation to mock a MyBatis interface (e.g. DAO). Let's say I have an interface like this one:
#Mapper
#Repository
public interface OrderDAO {
int insertOrder(#Param("order") Order order);
}
I'd like to execute an integration test and mock this mapper within my OrderService that contains a field of type OrderDAO.
My integration test is annotated with #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) and contains this field:
#MockBean
private OrderDAO orderDAO;
When I run a test such as this and look at what the references to the OrderDAO objects are, in my integration test I see this:
com.example.dao.OrderDAO$MockitoMock$1819884459
But inside my OrderService class the field is this:
com.sun.proxy.$Proxy179 (org.apache.ibatis.binding.MapperProxy#37d9310e)
So, calls to Mockito.verify obviously don't work because my mock has not been injected into my OrderService class. Now, very oddly, I found that adding this code makes everything work:
#TestConfiguration
static class MockConfig {
#Bean
public OrderDAO orderDAO() {
return Mockito.mock(OrderDAO.class);
}
}
Adding this nested class along with adding the ContextConfiguration annotation on the integration test class, and now the object that gets injected into the OrderService class is the MockitoMock -- the same object that is referenced by the MockBean annotated field in the test class. I didn't have to do this with Spring Boot 1.2.6 and earlier and I couldn't find any reference to a change that would have caused this (although perhaps I didn't search long enough).
So, I am wondering if I am doing something incorrectly, or, am I missing something I should be doing? It seems like this should just work like it did before, which the need for this extra nested TestConfiguration class. Appreciate any insights anyone can provide. Thanks.
As mentioned by ave in the comments, I had to add the name to the mock bean annotation to get it to work
#MockBean(name = "orderDAO")
private OrderDAO orderDAO;

Data JPA Test with reactive spring

I want to test the repository layer and I'm using spring webflux. My test class is as follows
#RunWith(SpringRunner.class)
#DataJpaTest
public class DataTester {
#Autowired
private MyRepository repository;
#Test
.....
}
Even though this would work in spring-mvc when using spring-weblux I get the following error.
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.context.ApplicationContextException: Unable to start ReactiveWebApplicationContext due to missing ReactiveWebServerFactory bean.
How to resolve this? If I am to start the whole application context with #SpringBootApplication it works. Any other options without using that?
The reason for this was that in the application.properties the application type was set as reactive.
spring.main.web-application-type=reactive
This tries to auto configure a web server in this case a reactive web server. As #DataJpaTest does not provide a bean for that, this fails. This can be fixed in either two ways.
One is by Adding an application.properties file in the resources directory of the test package and setting the value as,sprig.main-web-application-type=none solves this issue.
Or we can simple pass a property value to the annotation as follows. #DataJpaTest(properties = "spring.main.web-application-type=none")
If you are using Spring Boot 2+ then only #DataJpaTest is enough on test class.
So your test class should be
#DataJpaTest
public class DataTester {
#Autowired
private MyRepository repository;
#Test
.....
}

Spring Annotation #WebMvcTest does not work in an app that has Jpa repositories

I have a Spring App that uses JPA repositories (CrudRepository interfaces). When I try to test my controller using the new Spring test syntax #WebMvcTest(MyController.class), it fails coz it tries to instantiate one of my service class that uses JPA Repository, does anyone has any clues on how to fix that? The app works when I run it.
Here is the error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myapp.service.UserServiceImpl required a bean of type 'com.myapp.repository.UserRepository' that could not be found.
Action:
Consider defining a bean of type 'com.myapp.repository.UserRepository' in your configuration.
According to the doc
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This annotion only apply on the Spring MVC components.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
I was able to unit test a Rest Controller by implementing junit 5 and using #SpringJUnitConfig along with #WebMvcTest. I am using Spring Boot 2.4.5 and this is my example:
#SpringJUnitConfig
#WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {
#Autowired
private MockMvc mockMvc;
// This is a Mock bean of a Spring Feign client that calls an external Rest Api
#MockBean
private LoginServiceClient loginServiceClient;
// This is a Mock for a class which has several Spring Jpa repositories classes as dependencies
#MockBean
private OrderService orderService;
#DisplayName("should create an order")
#Test
void createOrder() throws Exception {
OrderEntity createdOrder = new OrderEntity("123")
when(orderService.createOrder(any(Order.class))).thenReturn(createdOrder);
mockMvc.perform(post("/api/v1/orders").contentType(MediaType.APPLICATION_JSON).content("{orderId:123}"))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))TODO: here it will go the correlationId
.andExpect(jsonPath("$.orderId").value("123"));
}
}
Please only use #SpringBootTest when you are implementing integration tests.
I faced this same problem. Using #SpringBootTest and #AutoConfigureMockMvc worked perfectly for me.

Resources