I am unit testing the spring MVC controller in my small app, but getting the following error:
INFO: **Initializing Spring FrameworkServlet ''**
Oct 30, 2015 5:37:38 PM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization started
Oct 30, 2015 5:37:38 PM org.springframework.test.web.servlet.TestDispatcherServlet initServletBean
INFO: FrameworkServlet '': initialization completed in 2 ms
Running com.sky.testmvc.product.controller.ProductSelectionControllerTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.031 sec <<< FAILURE!
testRoot(com.sky.testmvc.product.controller.ProductSelectionControllerTest) Time elapsed: 0.031 sec <<< FAILURE!
*java.lang.AssertionError: **Status expected:<200> but was:<204>***
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$5.match(StatusResultMatchers.java:549)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
The rest controller is below:
#RestController
public class ItemRestController {
#Autowired
ItemService catalogueService;
#Autowired
LocationService locationService;
#RequestMapping(value = "/product/{customerId}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
public ResponseEntity<List<Item>> listCustomerProducts(#PathVariable("customerId") int customerId) {
Integer customerLocation=(Integer) locationService.findLocationByCustomerId(customerId);
List<Product> products = catalogueService.findCustomerProducts(customerLocation);
if(products.isEmpty()){
return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<Product>>(products, HttpStatus.OK);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {TestContext.class, AppConfiguration.class}) //load your mvc config classes here
public class ItemControllerTest {
private MockMvc mockMvc;
#Autowired
private ItemService productServiceMock;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
Mockito.reset(productServiceMock);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testRoot() throws Exception
{
Product prod1= new Product(1L,"Orange", new ProductCategory(1,"Sports"),new CustomerLocation(2,"London"));
Product prod2= new Product(2L,"Banana", new ProductCategory(1,"Sports"),new CustomerLocation(2,"London"));
when(productServiceMock.findCustomerProducts(1L)).thenReturn(Arrays.asList(prod1, prod2));
mockMvc.perform(get("/product/{id}", 1L))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].productId", is(1)))
.andExpect(jsonPath("$[0].productName", is("Orange")))
.andExpect(jsonPath("$[1].productId", is(2)))
.andExpect(jsonPath("$[1].productName", is("Banana")));
verify(productServiceMock, times(1)).findCustomerProducts(1L);
verifyNoMoreInteractions(productServiceMock);
}
}
See the TestContext below:
#Configuration
public class TestContext {
private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages";
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
#Bean
public ItemService catalogueService() {
return Mockito.mock(ItemService .class);
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.uk.springmvc")
public class AppConfiguration extends WebMvcConfigurerAdapter{
private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/views/";
private static final String VIEW_RESOLVER_SUFFIX = ".jsp";
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);
registry.viewResolver(viewResolver);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public SimpleMappingExceptionResolver exceptionResolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties exceptionMappings = new Properties();
exceptionMappings.put("java.lang.Exception", "error/error");
exceptionMappings.put("java.lang.RuntimeException", "error/error");
exceptionResolver.setExceptionMappings(exceptionMappings);
Properties statusCodes = new Properties();
statusCodes.put("error/404", "404");
statusCodes.put("error/error", "500");
exceptionResolver.setStatusCodes(statusCodes);
return exceptionResolver;
}
}
As stated earlier, the application works and I can read the json returned via the browser and its displayed in my angularjs view, but the unit test is not working. Error is java.lang.AssertionError: Status expected:<200> but was:<204>.
204 - response is empty ...
Could be > Initializing Spring FrameworkServlet '' i.e. framework servlet is not initialized? Not sure.
Any help will be appreciated
My Rest Coontroller
#RestController
public class ItemRestController {
#Autowired
ItemService catalogueService; //Service which will do product retrieval
#Autowired
LocationService locationService; //Service which will retrieve customer location id using customer id
//-------------------Retrieve All Customer Products--------------------------------------------------------
#RequestMapping(value = "/product/{customerId}", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET)
public ResponseEntity<List<Product>> listCustomerProducts(#PathVariable("customerId") int customerId) {
Integer customerLocation=(Integer) locationService.findLocationByCustomerId(customerId);
List<Product> products = catalogueService.findCustomerProducts(customerLocation);
if(products.isEmpty()){
return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<Product>>(products, HttpStatus.OK);
}
}
As we can see from the stack trace, the test fails because of the assertion error: it expects HTTP Status 200, but gets 204, which is HTTPStatus.NO_CONTENT, which you return in controller when the list is empty.
if(products.isEmpty()){
return new ResponseEntity<List<Product>>(HttpStatus.NO_CONTENT);
}
If you want your controller to return 200 you need your CatalogueService mock to return something...
In your test, you are mocking ItemService (using productServiceMock), but your controller is calling instance of CatalogueService
Also make sure your mocks are properly injected, debugger is really your best friend here. I guess from your syntax, that you are using Mockito. In such case, mocked object have "$$EnhancedByMockito" in their getClass().getName() method
Related
I am trying to test the following controller:
#RepositoryRestController
#RequestMapping("movies")
public class MovieController {
private MovieService movieService;
private PagedResourcesAssembler<Movie> pagedAssembler;
private MovieResourceAssembler movieResourceAssembler;
#Autowired
public void setMovieService(MovieService movieService) {
this.movieService = movieService;
}
#Autowired
public void setPagedAssembler(PagedResourcesAssembler<Movie> pagedAssembler) {
this.pagedAssembler = pagedAssembler;
}
#Autowired
public void setMovieResourceAssembler(MovieResourceAssembler movieResourceAssembler) {
this.movieResourceAssembler = movieResourceAssembler;
}
// Return all movies with pagination
#GetMapping
public ResponseEntity<?> getAllMovies(Pageable pageable) {
Page<Movie> moviePage = this.movieService.getAllMovies(pageable);
// Remove some unnecessary fields
//this.movieService.removeUndesirableFieldsFromListing(moviePage);
return ResponseEntity.ok(this.pagedAssembler.toResource(moviePage, this.movieResourceAssembler));
}
}
and here's the test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class MovieControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MovieService movieService;
#Test
public void getAllMovies_PageableGiven_ShouldReturnMoviesPage() throws Exception {
List<Movie> movieList = new ArrayList<>();
movieList.add(new Movie());
movieList.add(new Movie());
Page<Movie> moviePage = new PageImpl<>(movieList);
given(this.movieService.getAllMovies(PageRequest.of(0,2)))
.willReturn(moviePage);
this.mockMvc.perform(get("http://localhost:8080/api/movies?page=1"))
.andExpect(status().isOk());
}
}
i got the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Page must not be null!
You could use Spring's mockMvc object injecting it inside your test class:
#Autowired
private MockMvc mockMvc;
I have just created a test method using mockMvc.perform method sending a page request:
My controller code:
#GetMapping(produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
public List<BaseResponse> listAllBase(
#PageableDefault(size = 50, page = 2) Pageable pageable) {
// logger.debug("paginación recibida :{}",pageable);
List<BaseResponse> findAllBases = baseService.findAllBases();
return findAllBases;
}
My test code:
mockMvc.perform(get("/base/?size=2&page=0")).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$", hasSize(2))) .andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name", equalToIgnoringCase("margarita")))
See full Class code in my GitHub repo:
Controller:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/main/java/com/mylab/cromero/controller/HelloWorldController.java#L53
Test class method:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/test/java/com/mylab/cromero/controller/RestTestIT.java#L66
Feel free to use it in your project :)
By reading these two Stackoverflow threads ([1] and [2]) and also Spring documentation, it seems that you should use Page with repositories. Example:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
For your case, it's recommended to use PagedListHolder, instead. Example:
List<Movie> movieList = new ArrayList<>();
movieList.add(new Movie());
movieList.add(new Movie());
PagedListHolder<Movie> holder = new PagedListHolder<>(movieList);
I have the following:
Controller class:
#Controller
#RequestMapping("/")
public class MainController {
#Inject
#Named("dbDaoService")
IDaoService dbDaoService;
#RequestMapping(method = RequestMethod.GET)
public String init(ModelMap model) {
List<Tags> tags = dbDaoService.getAllTags();
model.addAttribute("tags", tags);
return "create";
}
}
Service class:
#Service("dbDaoService")
public class DBDaoService implements IDaoService {
#PersistenceContext(unitName = "MyEntityManager")
private EntityManager entityManager;
#Override
#Transactional
public List<Tags> getAllTags() {
if(tags == null) {
TypedQuery<Tags> query = entityManager.createNamedQuery("Tags.findAll", Tags.class);
tags = query.getResultList();
}
return tags;
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class MainControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
#Test
public void init() throws Exception {
Tags tag1 = new Tags("first");
Tags tag2 = new Tags("second");
DBDaoService mock = org.mockito.Mockito.mock(DBDaoService.class);
when(mock.getAllTags()).thenReturn(Arrays.asList(tag1, tag2));
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("create"))
.andExpect(forwardedUrl("/WEB-INF/views/create.jsp"))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("first"))
)
)))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("second"))
)
)));
}
}
When I run MainControllerTest, it fails, because it gets "Tag" entities from DBDaoService (it means, from database), but I expect it will use mock service.
What's wrong?
The current test setup loads the spring configuration and uses the WebApplicationContext to handle request due to MockMvcBuilders.webAppContextSetup(context).build();. Thus mocked beans are ignored since they are not instructed to participate.
To interweave them the configuration should be updated to MockMvcBuilders.standaloneSetup(controller).build() instead.
The sample test case should look like below (note the additional mock for MainController and configuration update to MockMvcBuilders.standaloneSetup along with instruction to use Mockito annotation via MockitoAnnotations.initMocks)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class MainControllerTest {
private MockMvc mockMvc;
#InjectMocks
private MainController controller;
#Mock
private DBDaoService daoService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setViewResolvers(viewResolver)
.build();
Tags tag1 = new Tags("first");
Tags tag2 = new Tags("second");
when(daoService.getAllTags()).thenReturn(Arrays.asList(tag1, tag2));
}
#Test
public void init() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("create"))
.andExpect(forwardedUrl("/WEB-INF/views/create.jsp"))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("first"))
)
)))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("second"))
)
)));
}
}
I am able to run a Rest Controller PUT method that uses a Autowired #Service as expected via the Spring Boot Application. The same Autowiring is failing while trying to perform a Spring JUnit Test. I have tried reading through multiple threads with similar issues. I made sure I am NOT creating the #Service through the "new" keyword and I tried Context Configuration and other methods .. but all seems to be in vain. I am not sure where I am going wrong.
My Spring Boot Application class -
#SpringBootApplication
#ComponentScan({
"com.initech.myapp.*"
})
public class IngestionServerApplication {
private static final Log logger = LogFactory.getLog(IngestionServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(IngestionServerApplication.class, args);
logger.info("Ingestion Server Application started...");
}
}
Rest Controller Class -
package com.initech.myapp.ingestion.controller;
#RestController
public class IngestionController extends BaseRestController {
private static final Log logger = LogFactory.getLog(IngestionController.class);
// This variable is getting "null" autowiring if invoked
// via Spring Unit Testing framework while it is injected fine via
// Spring Boot app invocation.
#Autowired
public IngestionPayloadProcessor payloadProcessor;
#RequestMapping(path = "/ingest", method = RequestMethod.PUT,
consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE
},
produces = {
MediaType.APPLICATION_JSON_VALUE
})
public IngestionSuccessResponse ingest(#RequestHeader(value = "authToken", required = true) String authToken,
#RequestBody String jsonBody) throws Exception {
IngestionPayload ingestionPayload = new IngestionPayload();
ingestionPayload.setAuthToken(authToken);
ingestionPayload.setJsonBody(jsonBody);
IngestionSuccessResponse ingestionSuccessResponse = payloadProcessor.process(ingestionPayload);
return ingestionSuccessResponse;
}
}
Service Class
package com.initech.myapp.ingestion.app.service;
#Service
#ImportResource({"spring.xml"})
public class IngestionPayloadProcessor {
private static final Log logger = LogFactory.getLog(IngestionPayloadProcessor.class);
#Resource(name = "kafkaConfig")
private Properties kafkaConfig;
#Value("${kakfaTopic}")
private String kakfaTopic;
public IngestionSuccessResponse process(IngestionPayload ingestionPayload) throws Exception {
try {
IngestionSuccessResponse ingestionSuccessResponse = buildSuccessResponse(ingestionPayload);
return ingestionSuccessResponse;
}
catch (IllegalStateException e)
{
logger.error("Encountered exception while dropping message in Kafka... " + e.getMessage());
throw e;
}
}
}
private buildSuccessResponse() { ... }
Spring Unit Testing
#RunWith(SpringRunner.class)
#ContextConfiguration(locations = "classpath*:/spring.xml")
#WebMvcTest(IngestionServerApplication.class)
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(
new IngestionServiceController())
.build();
}
#Test
public void testIngestService() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
Error Logs
2016-08-10 19:24:36.500 DEBUG 7505 --- [ main] m.m.a.RequestResponseBodyMethodProcessor : Read [class java.lang.String] as "application/json" with [org.springframework.http.converter.StringHttpMessageConverter#49aa766b]
2016-08-10 19:24:36.510 DEBUG 7505 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public com.initech.myapp.ingestion.model.IngestionSuccessResponse com.initech.myapp.ingestion.app.controller.myappIngestionServiceController.ingest(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) throws java.lang.Exception]: java.lang.NullPointerException
2016-08-10 19:24:36.512 DEBUG 7505 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Invoking #ExceptionHandler method: public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> com.initech.myapp.base.controller.BaseRestController.handleException(java.lang.Exception,javax.servlet.http.HttpServletRequest)
This is the error handler...
2016-08-10 19:24:36.514 INFO 7505 --- [ main] p.d.i.a.c.myappIngestionServiceController : > handleNoResultException
2016-08-10 19:24:36.574 DEBUG 7505 --- [ main] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Written [{status=500, authToken=6acb1a5c-2ced-4690-95b3-eb7957c7c28a, error=null}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#50d3bf39]
java.lang.AssertionError: Status
Expected :200
Actual :500
Note that I have debugged through the test and I can see that the NullPointer exception is thrown at the below line in the Rest Controller Class as the payloadProcessor object is null.
IngestionSuccessResponse ingestionSuccessResponse = payloadProcessor.process(ingestionPayload);
=====
Unit Test with Mock objects: (This is working as expected)
#RunWith(SpringRunner.class)
#ActiveProfiles("dev")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class IngestionServerUnitTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private IngestionPayloadProcessor processor;
// I was able to get this to work by removing the setUp() method
// that was originally in my code. It was trying to build a new instance
// of the REST controller and then run the "perform" on top of it
// which was causing the test to fail I assume!
/*#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(
new IngestionServiceController())
.build();
}*/
#Test
public void testIngestService() throws Exception {
IngestionSuccessResponse ingestionSuccessResponse = new IngestionSuccessResponse();
ingestionSuccessResponse.setStatusCode("200");
ingestionSuccessResponse.setRequestId("6acb1a5c-2ced-4690-95b3-eb7957c7c28a");
ingestionSuccessResponse.setReceivedTimestamp("2016-08-09T19:43:30.02234312");
given(this.processor.process(anyObject())).willReturn(ingestionSuccessResponse);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization","5e18685c95b34690");
RequestBuilder requestBuilder = put("/ingest").content("<test>test data</test>").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
When you specify #WebMvcTest there are only certain components of your application that are added to the ApplicationContext. The annotation is actually a composition of a bunch of other annotations as described in the docs: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html
Based on this your IngestionPayloadProcessor doesn't get instantiated as a bean, and shouldn't as you are telling the test to only run tests for the web layer. What you need to do is specify a #MockBeanfor the IngestionPayloadProcessor within the test and then define a mock for the method that the controller is calling.
#RunWith(SpringRunner.class)
#WebMvcTest(IngestionServerApplication.class)
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private IngestionPayloadProcessor processor;
#Test
public void testIngestService() throws Exception {
given(this.processor.process(anyObject())).willReturn(new InjestionSuccessResponse());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
Details on the new features of Spring Boot 1.4 testing are here: https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4
* Update based on comments *
Realized you could just auto configure the MockMvc and not need to use the TestRestTemplate. I haven't tested this but it should work.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testIngestService() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
I'm using gradle and the following libs:
ext.library['jersey'] = "org.glassfish.jersey:project:2.13"
ext.library['jersey_jettison'] = "org.glassfish.jersey.media:jersey-media-json-jettison:2.13"
ext.library['jersey_jackson'] = "org.glassfish.jersey.media:jersey-media-json-jackson1:2.13"
ext.library['jersey_spring'] = "org.glassfish.jersey.ext:jersey-spring3:2.13"
ext.library['jersey_bean_validation'] = "org.glassfish.jersey.ext:jersey-bean-validation:2.13"
I created the bean validation structure, but its not validating at all. No error messages, nothing. This is the structure I've created:
The DTO
public class MergeSchedulesDto {
#NotNull(message = "validation.invalid.mergeFrom")
private Long mergeFrom;
#NotNull(message = "validation.invalid.mergeTo")
#NotEmpty(message = "validation.invalid.mergeTo")
private List<Long> mergeTo;
The Service
#Path("merge")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
public Response merge(#Valid MergeSchedulesDto dto, #QueryParam("units") List<Long> units) {
The config
public class ApplicationJAXRS extends Application {
public ApplicationJAXRS() {
}
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties
.put("jersey.config.server.provider.packages",
"com.sifionsolution.sig.academic.resource.service,com.sifionsolution.sig.integration.resource.filter,com.sifionsolution.sig.academic.param.converter,com.sifionsolution.sig.datatables.resource.service,com.sifionsolution.sig.datatables.converter");
return properties;
}
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(new Jackson1Feature());
singletons.add(new ValidationExceptionMapper());
return singletons;
}
}
EDIT I forgot the provider:
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
return Response.status(BAD_REQUEST).entity(exception.getMessage()).build();
}
}
EDIT 2: I removed the JUnit test because I didnt test using Jersey Test Framework.
The problem here is that the ValidationExceptionMapper is not beeing called.
Put "#Valid" in your, like this:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response insert(#Valid T obj) throws Exception{
...
}
This works here.
I have the following simple controller to catch any unexpected exceptions:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(Throwable.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ResponseEntity handleException(Throwable ex) {
return ResponseEntityFactory.internalServerErrorResponse("Unexpected error has occurred.", ex);
}
}
I'm trying to write an integration test using Spring MVC Test framework. This is what I have so far:
#RunWith(MockitoJUnitRunner.class)
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new ExceptionController(), statusController).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
I register the ExceptionController and a mock StatusController in the Spring MVC infrastructure.
In the test method I setup an expectation to throw an exception from the StatusController.
The exception is being thrown, but the ExceptionController isn't dealing with it.
I want to be able to test that the ExceptionController gets exceptions and returns an appropriate response.
Any thoughts on why this doesn't work and how I should do this kind of test?
Thanks.
I just had the same issue and the following works for me:
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(statusController)
.setControllerAdvice(new ExceptionController())
.build();
}
This code will add ability to use your exceptions controlled advice.
#Before
public void setup() {
this.mockMvc = standaloneSetup(commandsController)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
#Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(ExceptionController.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new ExceptionController(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Since you are using stand alone setup test you need to provide exception handler manually.
mockMvc= MockMvcBuilders.standaloneSetup(adminCategoryController).setSingleView(view)
.setHandlerExceptionResolvers(getSimpleMappingExceptionResolver()).build();
I had same problem a few days back, you can see my problem and solution answered by myself here Spring MVC Controller Exception Test
Hoping my answer help you out
Use Spring MockMVC to emulate a servletContainer to a point where you can incorporate any request filtering or exception handling tests in your unit tests suite.
You can configure this setup with the following approach:
Given a custom RecordNotFound exception...
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
... and a RecordNotFoundExceptionHandler
#Slf4j
#ControllerAdvice
public class BusinessExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
//Logs
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
//Http error message
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
...
}
Configure a tailored test context: set a #ContextConfiguration to specify the classes you need for your test. Set Mockito MockMvc as a servlet container emulator and set your tests fixture and dependencies.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
WebConfig.class,
HeaderFactory.class,
})
#Slf4j
public class OrganisationCtrlTest {
private MockMvc mvc;
private Organisation coorg;
#MockBean
private OrganisationSvc service;
#InjectMocks
private OrganisationCtrl controller = new OrganisationCtrl();
//Constructor
public OrganisationCtrlTest() {
}
....
Configure a mock MVC "servlet emulator": register handler beans in the context and build the mockMvc emulator (Note: there are two possible configuration: standaloneSetup or webAppContextSetup; refer to the documentation). The builder rightfully implements the Builder pattern so you can chain configuration commands for exception resolvers and handlers before calling build().
#Before
public void setUp() {
final StaticApplicationContext appContext = new StaticApplicationContext();
appContext.registerBeanDefinition("BusinessExceptionHandler",
new RootBeanDefinition(BusinessExceptionHandler.class, null, null));
//InternalExceptionHandler extends ResponseEntityExceptionHandler to //handle Spring internally throwned exception
appContext.registerBeanDefinition("InternalExceptionHandler",
new RootBeanDefinition(InternalExceptionHandler.class, null,
null));
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(getExceptionResolver(appContext))
.build();
coorg = OrganisationFixture.getFixture("orgID", "name", "webSiteUrl");
}
....
Get the exception resolver
private ExceptionHandlerExceptionResolver getExceptionResolver(
StaticApplicationContext context) {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());
resolver.setApplicationContext(context);
resolver.afterPropertiesSet();
return resolver;
}
Run your tests
#Test
public void testGetSingleOrganisationRecordAnd404() throws Exception {
System.out.println("testGetSingleOrganisationRecordAndSuccess");
String request = "/orgs/{id}";
log.info("Request URL: " + request);
when(service.getOrganisation(anyString())).
thenReturn(coorg);
this.mvc.perform(get(request)
.accept("application/json")
.andExpect(content().contentType(
.APPLICATION_JSON))
.andExpect(status().notFound())
.andDo(print());
}
....
}
Hope this helps.
Jake.
Try it;
#RunWith(value = SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { MVCConfig.class, CoreConfig.class,
PopulaterConfiguration.class })
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
This is better:
((HandlerExceptionResolverComposite) wac.getBean("handlerExceptionResolver")).getExceptionResolvers().get(0)
And do not forget to scan for #ControllerAdvice beans in your #Configuration class:
#ComponentScan(basePackages = {"com.company.exception"})
...tested on Spring 4.0.2.RELEASE