I have a Spring MVC 3.2 project that I would like to unit & integration tests. The problem is all the dependencies I have, makes testing extremely difficult even with Sprint-test.
I have a controller like this:
#Controller
#RequestMapping( "/" )
public class HomeController {
#Autowired
MenuService menuService; // will return JSON
#Autowired
OfficeService officeService;
#RequestMapping( method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE )
#ResponseBody
public AuthenticatedUser rootCall( HttpServletRequest request ) {
AuthenticatedUser authentic = new AuthenticatedUser();
Office office = officeService.findByURL(request.getServerName());
authentic.setOffice(office);
// set the user role to authorized so they can navigate the site
menuService.updateVisitorWithMenu(authentic);
return returnValue;
}
This will return a JSON object. I would like to test this call returns a 200 and the correct object with canned JSON. However, I have a lot of other classes called by those #Autowired classes, and even if I mock them like this:
#Bean public MenuRepository menuRepository() {
return Mockito.mock(MenuRepository.class);
}
this creates a lot of mocked classes. Here is how I am trying to test it:
#RunWith( SpringJUnit4ClassRunner.class )
#ContextConfiguration( classes = JpaTestConfig.class )
#WebAppConfiguration
public class HomeControllerTest {
private EmbeddedDatabase database;
#Resource
private WebApplicationContext webApplicationContext;
#Autowired
OfficeService officeService;
private MockMvc mockMvc;
#Test
public void testRoot() throws Exception { mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
.andExpect(content().string(<I would like canned data here>));
}
I can go thru and setup a H2 embeddeddatabase and populate it, but I wonder if that is really a test of this controller or the application? Can anyone recommend some better approaches to this integration test? How does one write unit tests for controllers?
Thank you!
Check out the spring show case project and take a look at controller test cases you will be able to understand and see standard way of testing controllers. MappingControllerTests.java has some json based controller testing
Related
I'm trying to test a spring rest controller class using JUnit, Mockito, Spring test and Spring Security test. The following is my rest controller class for which i'm performing the test;
#RestController
public class EmployeeRestController {
#Autowired
private EmployeeService employeeService;
#PreAuthorize("hasAnyRole('ROLE_EMPSUPEADM')")
#RequestMapping(value = "/fetch-timezones", method = RequestMethod.GET)
public ResponseEntity<List<ResponseModel>> fetchTimeZones() {
List<ResponseModel> timezones = employeeService.fetchTimeZones();
return new ResponseEntity<>(timezones, HttpStatus.OK);
}
}
The following is my test class;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringConfiguration.class})
#WebAppConfiguration
public class EmployeeRestControllerUnitTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Mock
private EmployeeService employeeService;
#InjectMocks
private EmployeeRestController employeeRestController;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
Mockito.reset(employeeService);
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.build();
}
#Test
#WithMockUser(roles = {"EMPSUPEADM"})
public void testFetchTimezones() {
try {
mockMvc.perform(get("/fetch-timezones"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(4)));
verify(emploeeService, times(1)).fetchTimeZones();
verifyNoMoreInteractions(employeeService);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I made the above test class by refering many tutorials. The problem is i'm not able to understand everything clearly. so, i'm having the following doubts.
I'm creating a mock of EmployeeService and injecting it into EmployeeRestController using #InjectMocks, then why i'm getting the following failure;
Wanted but not invoked:
careGroupService.fetchTimeZones();
-> at com.example.api.test
.restcontroller.EmployeeRestControllerUnitTest
.testFetchTimezones(EmployeeRestControllerUnitTest.java:73)
Actually, there were zero interactions with this mock.
How does MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); works exactly.
I know that MockMvcBuilders.standaloneSetup(employeeRestController) is for testing individual controller classes and spring configuration will not be available for this method. How can we provide spring configuraton for this method, is it possible.
Finally, how does this piece of code: Mockito.reset(employeeService); works.
1) you do verify for
verify(emploeeService, times(1)).fetchTimeZones();
but you didn't setup mock behaviour for it (before you call mockMvc.perform(get("/fetch-timezones"))).
List<ResponseModel> timezones = new ArrayList<>();
when(emploeeService.fetchTimeZones()).thenReturn(timezones );
2) create MockMvc from context. mockmvc emulates web container, use mock for all where is possible but supports http call and gave the possibility to call controller.
It stands up the Dispatcher Servlet and all required MVC components,
allowing us to test an endpoint in a proper web environment, but
without the overhead of running a server.
3) when you use:
#MockBean private EmployeeService employeeService;
you override real service. remove it from test class real service will be used in testing. Instead of use #Mock use #MockBean. #MockBean it's override by container, with #Mock you need to inject this into controller by reflection
or without spring boot with reflection:
#Before
public void init() {
MockitoAnnotations.initMocks(this);
Mockito.reset(employeeService);
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.build();
EmployeeRestController employeeRestController=
webAppContext.getBean(EmployeeRestController.class);
ReflectionTestUtils.setField(employeeRestController,
"employeeService",
employeeService);
}
4) Mockito.reset(employeeService);- you reset all behaviors that you setupped before. Mock contains information from when(), verify() and controls it , call reset - it's clean all information.
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;
I have a Spring MVC Controller which uses Pagination Support of Spring-Data:
#Controller
public class ModelController {
private static final int DEFAULT_PAGE_SIZE = 50;
#RequestMapping(value = "/models", method = RequestMethod.GET)
public Page<Model> showModels(#PageableDefault(size = DEFAULT_PAGE_SIZE) Pageable pageable, #RequestParam(
required = false) String modelKey) {
//..
return models;
}
}
And I'd like to test the RequestMapping using the nice Spring MVC Test Support. In order to keep these tests fast and isolated from all the other stuff going on, I do not want to create the complete ApplicationContext:
public class ModelControllerWebTest {
private MockMvc mockMvc;
#Before
public void setup() {
ModelController controller = new ModelController();
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void reactsOnGetRequest() throws Exception {
mockMvc.perform(get("/models")).andExpect(status().isOk());
}
}
This approach works fine with other Controllers, that do not expect a Pageable, but with this one I get one of these nice long Spring stacktraces. It complains about not being able to instantiate Pageable:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.data.domain.Pageable]: Specified class is an interface
at
.... lots more lines
Question: How do I change my test so the magic No-Request-Parameter-To-Pageable conversion happens properly?
Note: In the actual application everything is working fine.
Original answer:
The problem with pageable can be solved by providing a custom argument handler. If this is set you will run in a ViewResolver Exception (loop). To avoid this you have to set a ViewResolver (an anonymous JSON ViewResolver class for example).
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.setViewResolvers(new ViewResolver() {
#Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return new MappingJackson2JsonView();
}
})
.build();
Updated (2020):
It is not necessary to add the ViewResolver anymore.
Regarding the parallel answer:
It does not solve the problem for the original question to have this test running without ApplicationContext and/or friends.
Just add #EnableSpringDataWebSupport for test. Thats it.
For spring boot simply adding the ArgumentResolvers solved for me:
From code which triggered the error:
this.mockMvc = MockMvcBuilders.standaloneSetup(weightGoalResource).build();
To this, which works:
this.mockMvc = MockMvcBuilders.standaloneSetup(weightGoalResource)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.build();
Can Spring MVC bind HTTP headers to Java classes?
I've got three headers, and I'd like to marshall them into a POJO, much like you'd do with a form or a request body.
I can see two ways that you could achieve this with Spring and request or prototype scoped beans.
It is worth first being clear on the different scopes of beans and how Spring creates proxies for different scopes if you are not already.
The first method uses Spring Expression Language to directly reference the current HttpServletRequest instance.
#Component
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyClass
{
#Value({#request.getHeader('headerName')})
private String myHeaderValue;
public String getMyHeaderValue()
{
return myHeaderValue;
}
}
An alternative is to simply inject the current HttpServletRequest as a constructor parameter:
#Component
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyClass
{
private String myHeaderValue;
#Autowired
public MyClass(HttpServletRequest httpServletRequest)
{
this.myHeaderValue = httpServletRequest.getHeader("headerValue");
}
public String getMyHeaderValue()
{
return this.myHeaderValue;
}
}
You can then inject this bean into your Controller or Service beans as needed:
#Controller
public class MyController
{
#Autowired
private MyClass myClass;
}
Either method should let you achieve what you want, you can pick which best suits your requirements and preferences.
Am new to Spring MVC, i have written web servise using spring MVC and resteasy. My controller is working fine, now need to write testcase but i tried writtig but i never succed am also getting problem in autowiring.
#Controller
#Path("/searchapi")
public class SearchAPIController implements ISearchAPIController {
#Autowired
private ISearchAPIService srchapiservice;
#GET
#Path("/{domain}/{group}/search")
#Produces({"application/xml", "application/json"})
public Collections getSolrData(
#PathParam("domain") final String domain,
#PathParam("group") final String group,
#Context final UriInfo uriinfo) throws Exception {
System.out.println("LANDED IN get****************");
return srchapiservice.getData(domain, group, uriinfo);
}
}
can anyone give me sample code for Test case in spring mvc.
"Spring-MVC" Test case could seem like this using mock objects, for example we want to test my MyControllerToBeTest:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/spring.xml")
public class MyControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MyControllerToBeTested controller;
private AnnotationMethodHandlerAdapter adapter;
#Autowired
private ApplicationContext applicationContext;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
response.setOutputStreamAccessAllowed(true);
controller = new MyControllerToBeTested();
adapter = new AnnotationMethodHandlerAdapter();
}
#Test
public void findRelatedVideosTest() throws Exception {
request.setRequestURI("/mypath");
request.setMethod("GET");
request.addParameter("myParam", "myValue");
adapter.handle(request, response, controller);
System.out.println(response.getContentAsString());
}
}
but i don't have any experience with REST resource testing, in your case RestEasy.
If you want to test the full service inside the container you can have a look at the REST Assured framework for Java. It makes it very easy to test and validate HTTP/REST-based services.