spring boot test unable to inject TestRestTemplate and MockMvc - spring-mvc

I am using spring boot 1.4.0.RELEASE. I am writing tests for my controller class. I get the following exception.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.concur.cognos.authentication.service.ServiceControllerITTest': Unsatisfied dependency expressed through field 'restTemplate': No qualifying bean of type [org.springframework.boot.test.web.client.TestRestTemplate] found for dependency [org.springframework.boot.test.web.client.TestRestTemplate]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.test.web.client.TestRestTemplate] found for dependency [org.springframework.boot.test.web.client.TestRestTemplate]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Here is my test class
public class ServiceControllerITTest extends ApplicationTests {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private MockMvc mvc;
#Test
public void exampleTest() throws Exception {
// test
}
}
ApplicationTests.java
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
//#DirtiesContext
public class ApplicationTests {
#Autowired
Environment env;
#Test
public void contextLoads() {
}
}

TestRestTemplate is only auto-configured when #SpringBootTest has been configured with a webEnvironment that means it starts the web container and listens for HTTP requests. For example:
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)

If you read the java doc of SpringBootTest annotation, it says that annotation provides below features (not listing all of them here, but only what are relevant to the question.)
Provides support for different webEnvironment modes, including the ability to start a fully running web server listening on a defined or random port.
Registers a TestRestTemplate and/or WebTestClient bean for use in web tests that are using a fully running web server listening on a defined or random port.
So #SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) provides the ability to autowire TestRestTemplate because it starts a fully running web server [as mentioned in #AndyWilkinson' answer as well].
But if you want to autowire MockMvc as well in same TestClass then use
#AutoConfigureMockMvc annotation over TestClass.
This is how a Test class may look like:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class SBTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private MockMvc mvc;
// tests
}

To work with that, don't use a deprecated TestRestTemplate.
Deprecated:
import org.springframework.boot.test.TestRestTemplate;
Correct:
import org.springframework.boot.test.web.client.TestRestTemplate;
Then you can use the #Autowired annotation in your class:
#Autowired
private TestRestTemplate restTemplate;
And don't use:
#Autowired
private MockMvc mvc;
Both together doesn't work.

According to Spring boot documentation :
You can also auto-configure MockMvc in a non-#WebMvcTest (e.g. SpringBootTest) by annotating it with #AutoConfigureMockMvc.

Related

JHipster RestController WebMvcTest fails to load

I'm using JHipster 6.2.0 and I want to create a WebMvcTest for a RestController. When I run the test, the application context does not load and I get:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.mytest.security.jwt.TokenProvider' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I'm using junit5 and spring boot 2.1.7
I tried adding #MockBean TokenProvider tokenProvider, and then the next missing dependency is CorsFilter. Mocked that but then I get a IllegalArgumentException because the mocked CorsFilter does not have a filter name defined. I thought about disabling the security filters, but I would like to keep the test configurations as vanilla as possible, utilizing the WebMvcTest default configurations.
I reproduced the issue with this simple example.
package com.example.mytest.web.rest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api")
public class MyController {
#GetMapping("/my")
public MyResponse get() {
MyResponse myResponse = new MyResponse();
myResponse.text = "test";
return myResponse;
}
static class MyResponse {
String text;
}
}
package com.example.mytest.web.rest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.junit.jupiter.api.Assertions.*;
#WebMvcTest(MyController.class)
class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
void get() {
// doesn't run, due to NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.mytest.security.jwt.TokenProvider' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
}
}
I expect that I should be able to run a GET mockMvc test without having to mock a CorsFilter name. This mocking would need to be done in every WebMvcTest and is very repetitive. I don't want to run a full #SpringBootTest for every RestController. All the generated RestController tests are #SpringBootTests and run in a full application context.
Some similar questions:
Spring Boot 2.1 - #WebMvcTest without Spring Security Auto-Configuration
Security configuration on Spring Boot 2.1 - How to keep the default instead of my custom for tests?

The #PropertyMapping annotation #WebMvcTest cannot be used in combination with the #Component annotation #Configuration

I have a Spring MVC test:
#RunWith(SpringRunner.class)
#WebMvcTest
#Configuration
public class InventoryControllerIntegrationTest {
#MockBean
private InventoryService inventoryService;
#Autowired
private MockMvc mockMvc;
#Test
public void shouldReturnInventory() throws Exception {
this.mockMvc.perform(get("/inventory")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello World")));
}
}
And a corresponding controller.
After running the test I get the exception:
2018-01-27 21:28:25.099 ERROR 12470 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener#6df37f7a] to prepare test instance [com.video.rental.store.videorentalstore.inventory.InventoryControllerIntegrationTest#21d3d6ec]
java.lang.IllegalStateException: The #PropertyMapping annotation #WebMvcTest cannot be used in combination with the #Component annotation #Configuration
at org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer$PropertyMappingCheckBeanPostProcessor.postProcessBeforeInitialization(PropertyMappingContextCustomizer.java:99) ~[spring-boot-test-autoconfigure-1.5.9.RELEASE.jar:1.5.9.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:409) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
How do I go around this and add the mock bean inventoryService?

Setting up MockMvc with #WebMvcTest in Spring Boot 1.4 MVC Testing

I have few working code to set up MockMVc in different ways with the new Spring Boot 1.4 #WebMvcTest. I understand the standaloneSetup approach. What I want to know is the difference between setting up MockMvc through WebApplicationContext and by autowiring MockMvc.
Code Snippet 1: MockMvc through WebApplicationContext Setup
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = ProductController.class)
public class ProductControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private ProductService productServiceMock;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testShowProduct() throws Exception {
Product product1 = new Product();
/*Code to initialize product1*/
when(productServiceMock.getProductById(1)).thenReturn(product1);
MvcResult result = mockMvc.perform(get("/product/{id}/", 1))
.andExpect(status().isOk())
/*Other expectations*/
.andReturn();
}
}
As per WebMvcTest API documentation, By default, tests annotated with #WebMvcTest will also auto-configure Spring Security and MockMvc. So, I expected a 401 Unauthorized status code here, but the test passes with a 200 status code.
Next, I tried auto wiring MockMvc, but the test fails with 401 Unauthorized status code, unless I add #AutoConfigureMockMvc(secure=false) or update the #WebMvcTest annotation to disable security:
#WebMvcTest(controllers = IndexController.class, secure = false)
Following is the code that passes ONLY AFTER explicitly disabling security.
Code Snippet 2: MockMvc through Autowiring
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = ProductController.class)
#AutoConfigureMockMvc(secure=false)
public class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private ProductService productServiceMock;
#Test
public void testShowProduct() throws Exception {
Product product1 = new Product();
/*Code to initialize product1*/
when(productServiceMock.getProductById(1)).thenReturn(product1);
MvcResult result = mockMvc.perform(get("/product/{id}/", 1))
.andExpect(status().isOk())
/*Other expectations*/
.andReturn();
}
}
So my questions are:
Why didn't Code snippet 1 report a a 401 Unauthorized status code error while auto wiring MockMvc did. Also reiterating what the official doc says By default, tests annotated with #WebMvcTest will also auto-configure Spring Security and MockMvc. But, in this case it appears #WebMvcTest has nothing to do with auto-configuring Spring Security (Because Code Snippet 1 passes without any 401 error). It finally boils down to how I set up the MockMvc. Am I correct here?
What are the differences/objectives between/of both the approaches?
How does disabling security via #AutoConfigureMockMvc(secure=false) differs from doing through #WebMvcTest(controllers = IndexController.class, secure = false). Which one is the preferred approached or when (or where) to use them?
I also come across similar problem. #WebMvcTest auto configures Spring Security with basic auth but I have a WebSecurityConfig class that extends WebSecurityConfigurerAdapter. In this class I disabled basic auth and configured token base security. That means WebSecurityConfig class is not used to configure Spring Security.
To resolve the problem, I added #ContextConfiguration to my unit test class and added mocks of dependencies of WebSecurityConfig class.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = CategoryRestService.class)
#ContextConfiguration(classes = {MjApplication.class, WebSecurityConfig.class})
public class CategoryRestServiceTest {
#MockBean
private CategoryRepository repository;
#MockBean
CurrentUserDetailsService currentUserDetailsService;
#MockBean
TokenAuthProvider tokenAuthProvider;
#Autowired
MockMvc mockMvc;
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void getCategories() throws Exception {
Category category1 = new Category();
category1.setName("Test Category 1");
category1.setId(1L);
Category category2 = new Category();
category2.setName("Test Category 2");
category2.setId(2L);
List<Category> categoryList = new ArrayList<Category>();
categoryList.add(category1);
categoryList.add(category2);
given(this.repository.findAll())
.willReturn(categoryList);
mockMvc.perform(get("/public/rest/category"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[0].name", is("Test Category 1")))
.andExpect(jsonPath("$[1].id", is(2)))
.andExpect(jsonPath("$[1].name", is("Test Category 2")));
}
}
According to this issue in github
https://github.com/spring-projects/spring-boot/issues/5476
#WebMvcTest auto configures by default, a basic auth when spring-security-test is in the class path
Answering your questions:
In the code snippet 1, you not injected the MockMvc in your test class, you should add .apply(springSecurity()) in the builder on the setup method, so spring would use the basic configuration (not your custom security configuration if you have one)
both approaches does basically the same thing, the difference is that the second comes with the basic auth already in the MockMvc, that is why you have to use the secure=false
From the documentation:
By default, tests annotated with #WebMvcTest will also auto-configure
Spring Security and MockMvc (include support for HtmlUnit WebClient
and Selenium WebDriver). For more fine-grained control of MockMVC the
#AutoConfigureMockMvc annotation can be used.
I'm not sure this is directly related, but there is an outstanding bug where, if using spring boot and #WebMvcTest, your custom #EnableWebSecurity config class will be ignored. A couple workarounds are mentioned in the bug report. I'm using:
#WebMvcTest(includeFilters = #Filter(classes = EnableWebSecurity.class))
I don't know if this is correct way or not but I could disable configurations class by using like below
#WebMvcTest(ProductController.class)
#ContextConfiguration(classes = ProductController.class)
public class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private ProductService productServiceMock;

How to wire AuthenticationManager for Spring AbstractPreAuthenticatedProcessingFilter

I have a class derived from AbstractPreAuthenticatedProcessingFilter in my Spring Security Filter Chain. The purpose of this filter is to massage role data left in a special Principal object by a corporate authentication service into a Collection so SpringSecurity can use them.
However, I cannot get past this exception:
Caused by: java.lang.IllegalArgumentException: An AuthenticationManager must be set
at org.springframework.util.Assert.notNull(Assert.java:112) ~[spring-core-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.afterPropertiesSet(AbstractPreAuthenticatedProcessingFilter.java:97) ~[spring-security-web-4.0.1.RELEASE.jar:4.0.1.RELEASE]
I am using Java config, not XML config. My code following the example of How To Inject AuthenticationManager using Java Configuration in a Custom Filter is as follows:
the security configurer adaptor
#Configuration
#EnableWebSecurity
public class MyWebSecurityAdaptor extends WebSecurityConfigurerAdapter {
...
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
The filter class itself:
#Component
public class MyPreauthFilter extends AbstractPreAuthenticatedProcessingFilter {
...
#Autowired
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
If instead of the code in Item 1 above, I try the following:
#Autowired
#Override
protected AuthenticationManager authenticationManager() throws Exception
{
// TODO Auto-generated method stub
return super.authenticationManager();
}
Then the error changes.
It then becomes:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.authentication.AuthenticationManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate}
I guess that makes sense, this way does not define a bean. But then why didn't the original way, which DID define a bean, fail?
Instead of adding 'myAuthernticationManager' to the WebSecurityConfigurerAdapter class. Add this directly to you filter class and autowire it.
#Autowired
#Override
public void setMyAuthenticationManager(MyAuthenticationManager myAuthenticationManager) {
this.myAuthenticationManager = myAuthenticationManager;
super.setAuthenticationManager(this.myAuthenticationManager);
}
Remove all the code related to myAuthenticationManager from your WebSecurityConfigurerAdapter.

Autowiring inside contextInitialized method of Context Listener

I am trying to autowiring my bean inside contextInitialized() method of my custom Context Listener class, but it is not working.
public class CustomContextListener extends ContextLoaderListener {
#Autowired
private MyBeanClass bean;
#Override
public void contextInitialized(javax.servlet.ServletContextEvent event) {
super.contextInitialized(event);
//call to my method.
bean.mymethod();
}
But here it is not getting autowired, i am getting null object for MyBeanClass reference.
How to autowire a class at the time of tomcat startup.
Please provide me alternate places where i can execute some code using autowiring at the time of server startup (here tomcat).
I would suggest to use the WebApplicationContext method to find the bean and then invoke.
WebApplicationContext servletContext = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
bean = (MyBeanClass) servletContext.getBean("myBeanClass");
bean.yourMethod();
More systematic to use... :)

Resources