MockMvc and #ControllerAdvice doesn't work in tests - java

I'm testing my RestController with mockMvc. I have a global RestExceptionHandler to resolve all exceptions. In my RestController I throw custom Exception RequestValidationException like this:
#ApiOperation("Search something")
#RequestMapping(path = "/search", method = RequestMethod.POST)
public CompletableFuture<SomeResponse> search(
#RequestBody #Validated SearchRequest request, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new RequestValidationException(bindingResult);
}
return searchService.search(request);
}
And when i pass empty request it must throw RequestValidationException(bindingResult)
but when i start tests they fall in that place where i throw Exception instead to resolve it.
i try to configure my mockMvc like this:
#RunWith(SpringRunner.class)
public class SearchControllerTest {
private MockMvc mockMvc;
#InjectMocks
protected SearchController searchController;
#MockBean
private SearchService searchService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(searchController)
.setHandlerExceptionResolvers(getHandlerExceptionResolver())
.build();
}
private HandlerExceptionResolver getHandlerExceptionResolver() {
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", RestExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
return webMvcConfigurationSupport.handlerExceptionResolver();
}
but it doesnt help. i'm getting an Exception insted json with message.
My RequestValidationExceptionHandler:
#Component
public class RequestValidationExceptionHandler implements ApiExceptionHandler {
#Override
public ResponseEntity<ApiResponse> process(Throwable throwable) {
RequestValidationException e = (RequestValidationException) throwable;
if (e.getBindingResult() != null) {
return new ResponseEntity<>(ApiResponse.badRequest(e.getBindingResult()), HttpStatus.OK);
}
return new ResponseEntity<>(ApiResponse.badRequest(throwable, ApiResponseCode.BAD_REQUEST), HttpStatus.OK);
}
#Override
public Class<? extends Throwable> getSupportedException() {
return RequestValidationException.class;
}
}
2) My #ControllerAdvice:
#Slf4j
#ControllerAdvice
#SuppressWarnings({"checkstyle:JavadocMethod", "checkstyle:MultipleStringLiterals"})
public class RestExceptionHandler {
#Autowired
private ExceptionHandlerRegistry handlerRegistry;
#ExceptionHandler
public ResponseEntity handleThrowable(Throwable throwable, WebRequest request) {
request.setAttribute(Constants.ERROR_ATTRIBUTE_NAME, throwable, RequestAttributes.SCOPE_REQUEST);
Throwable ex = throwable instanceof CompletionException ?
ObjectUtils.defaultIfNull(throwable.getCause(), throwable) : throwable;
for (ApiExceptionHandler handler : handlerRegistry.getHandlers()) {
if (handler.isSupported(ex)) {
return handler.process(ex);
}
}
return new ResponseEntity<>(ApiResponse.badRequest(throwable, ApiResponseCode.SERVER_ERROR), HttpStatus.OK);
}
}
3) And ExceptionHandlerRegistry :
#Component
public class ExceptionHandlerRegistry {
#Getter
private final List<ApiExceptionHandler> handlers;
#Autowired
public ExceptionHandlerRegistry(List<ApiExceptionHandler> handlers) {
this.handlers = ObjectUtils.defaultIfNull(handlers, Collections.emptyList());
}
}
The Error message:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is ru.filit.mvideo.mb2c.api.exceptions.RequestValidationException
UPDATE
So after some discussion with #MichaelMichailidis, i try to do this, i just add an inner #Configuration class with needed beans:
#TestConfiguration
static class SearchControllerTestConfiguration {
#Bean
public RequestValidationExceptionHandler requestValidationExceptionHandler(){
return new RequestValidationExceptionHandler();
}
#Bean
public ExceptionHandlerRegistry getExceptionHandlerRegistry(final RequestValidationExceptionHandler requestValidationExceptionHandler){
return new ExceptionHandlerRegistry(Collections.singletonList(requestValidationExceptionHandler));
}
#Bean
public RestExceptionHandler getRestExceptionHandler(){
return new RestExceptionHandler();
}
}
and my test pass. But i can't understand why test were working without configuration before i add #ControllerAdvice?

You can try importing your exception handler in your test class:
#RunWith(SpringRunner.class)
#Import(RestExceptionHandler.class) // EXCEPTION HANDLER CLASS
public class SearchControllerTest {

Related

Factory design patter Spring Boot double bean

#Component
public abstract class CommandBase {
#Autowired
WebServiceProxy nbiService;
#Autowired
OperationCacheRepository cacheRepository;
public CommandBase(
WebServiceProxy nbiService,
OperationCacheRepository cacheRepository) {
this.nbiService = nbiService;
this.cacheRepository = cacheRepository;
}
public abstract void executeSPV(SpeedTestDTO stDTO) throws NBIException;
public abstract long executeGPV(long guid, OperationCache operationCache) throws NBIException;
#Slf4j
public class DownloadDiagnosticsCommand extends CommandBase {
public DownloadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
// some executable code
}
}
#Slf4j
public class UploadDiagnosticsCommand extends CommandBase {
public UploadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
//some executable code
}
}
#Component
public class RFACommandFactory {
#Autowired
WebServiceProxy nbiServiceProxy;
#Autowired
OperationCacheRepository cacheRepository;
public final CommandBase createCommand(final String measureType) {
if ("download".equalsIgnoreCase(measureType)) {
return new DownloadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
} else if ("upload".equalsIgnoreCase(measureType)) {
return new UploadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
}
return null;
}
}
Calling method executeSPV from abstract class
#RestController
#RequestMapping("/rfa/speedtest/v1")
#Slf4j
public class Controller {
#Autowired
CommandBase command;
#Autowired
RFACommandFactory rfaCommandFactory;
#PostMapping(value = "{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
private ResponseEntity<String> post(
#PathVariable String assetId,
#RequestBody Payload payload) {
log.info("Received new payload:{}", payload);
command = rfaCommandFactory.createCommand(speedTestDTO.getType());
try {
command.executeSPV(speedTestDTO);
} catch (NBIException e) {
log.info("NBIException", e);
return new ResponseEntity(payload, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(payload, HttpStatus.CREATED);
}
}
If I remove #Componet from Upload and Download classes I receive error I need to add Bean for abstrcat class CommndBase
If I use #Compoment on Upload and Download classes I receive dual Bean is useed...
Field command in .Controller required a single bean, but 2 were found:
You should not use #Component for abstract class, because Spring context will not be able to initialize that bean. You should remove it then.
Another thing is the way you want to implement a factory pattern here - I recommend you the way described here: https://stackoverflow.com/a/39361500/14056755, refactored version https://stackoverflow.com/a/55060326/14056755.

No mapping for request with mockmvc

Currently struggling with problem when I get 'mapping error for request' with following controller/test configuration.
Controller:
#Slf4j
#Validated
#RestController
#RequiredArgsConstructor
public class AdtechController {
private final AdtechService adtechService;
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
log.trace("execute submitSession with {}", requestDto);
ResponseDtoresponse = adtechService.submitSession(requestDto);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#ExceptionHandler(AdtechServiceException.class)
public ResponseEntity<AdtechErrorResponse> handleAdtechServiceException(AdtechServiceException e) {
return new ResponseEntity<>(new AdtechErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#SpringJUnitConfig({AdtechTestConfig.class})
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#Test
public void testSubmitSession() throws Exception {
RequestDto requestDto = new RequestDto ();
requestDto.setKyivstarId("1123134");
requestDto.setMsisdn("123476345242");
requestDto.setPartnerId("112432523");
requestDto.setPartnerName("125798756");
String request = OBJECT_MAPPER.writeValueAsString(requestDto);
System.out.println("REQUEST: " + request);
String response = OBJECT_MAPPER.writeValueAsString(new ResponseDto("123"));
System.out.println("RESPONSE: " + response);
mockMvc.perform(post("/subscriber/session")
.content(MediaType.APPLICATION_JSON_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString(response)));
}
}
Configuration:
#Configuration
public class AdtechTestConfig {
#Bean
public AdtechService adtechTestService() {
return requestDto -> new AdtechResponseDto("123");
}
}
After test execution I get No mapping for POST /subscriber/session
The reason for the struggle is that my code from other modules with the same configuration works fine. Can somebody point out what am I missing ? Thanks in advance!
Apparently you are loading a configuration class to mock beans, this interferes with the other parts of Spring Boot and probably leads to partially loading your application. I suspect only the mocked service is available.
Instead of the test configuration use #MockBean to create a mock for the service and register behaviour on it.
#SpringBootTest
#AutoConfigureMockMvc
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#MockBean
private AdtechService mockService;
#BeforeEach
public void setUp() {
when(mockService.yourMethod(any()).thenReturn(new AdtechResponseDto("123"));
}
#Test
public void testSubmitSession() throws Exception {
// Your original test method
}
}
If the only thing you want to test is your controller you might also want to consider using #WebMvcTest instead of #SpringBootTest.
#WebMvcTest(AdTechController.class)
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#MockBean
private AdtechService mockService;
#BeforeEach
public void setUp() {
when(mockService.yourMethod(any()).thenReturn(new AdtechResponseDto("123"));
}
#Test
public void testSubmitSession() throws Exception {
// Your original test method
}
}
This will load a scaled-down version of the context (only the web parts) and will be quicker to run.
try this:
#Slf4j
#Validated
#RestController
#RequiredArgsConstructor
public class AdtechController {
private AdtechService adtechService;
public AdtechController (AdtechService adtechService) {
this.adtechService= adtechService;
}
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
log.trace("execute submitSession with {}", requestDto);
ResponseDtoresponse = adtechService.submitSession(requestDto);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#ExceptionHandler(AdtechServiceException.class)
public ResponseEntity<AdtechErrorResponse> handleAdtechServiceException(AdtechServiceException e) {
return new ResponseEntity<>(new AdtechErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#SpringJUnitConfig({AdtechTestConfig.class})
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#Autowired
private AdtechService adtechService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.mvc = MockMvcBuilders.standaloneSetup(new AdtechController(adtechService)).build();
}
#Test
public void testSubmitSession() throws Exception {
RequestDto requestDto = new RequestDto ();
requestDto.setKyivstarId("1123134");
requestDto.setMsisdn("123476345242");
requestDto.setPartnerId("112432523");
requestDto.setPartnerName("125798756");
String request = OBJECT_MAPPER.writeValueAsString(requestDto);
System.out.println("REQUEST: " + request);
String response = OBJECT_MAPPER.writeValueAsString(new ResponseDto("123"));
System.out.println("RESPONSE: " + response);
mockMvc.perform(post("/subscriber/session")
.content(MediaType.APPLICATION_JSON_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString(response)));
}
}
Is the AdtechTestConfig.class introducing the /ad-tech path segment in to your test request? If so, this is why your test is trying the path /ad-tech/subscriber/session instead of /subscriber/session.
If this is actually the correct uri, then you may add #RequestMapping to the controller like below or just to the post method itself
#Slf4j
#Validated
#RestController
#RequestMapping("/ad-tech")
#RequiredArgsConstructor
public class AdtechController {
private final AdtechService adtechService;
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
...

How to perform JUnit test on Custom Exception

I have a Custom exception that handles the expected errors of my program and here is the code.
#ControllerAdvice
#RestController
public class DashboardException {
#ExceptionHandler({Exception.class, IOException.class, ParseException.class, JsonProcessingException.class})
public final ResponseEntity<ErrorDetails> dataNotFoundException(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails();
errorDetails.setTimestamp(new Date().toString());
errorDetails.setMessage(ex.getMessage());
errorDetails.setPath(request.getDescription(false));
errorDetails.setStatus(HttpStatus.BAD_REQUEST.value());
errorDetails.setError(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
My problem is on how to properly unit test this class. This is what I have made so far to make it cover, but with no luck.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { DashboardException.class, TestConfiguration.class, DataController.class })
public class testDashboardException {
private MockMvc mockMvc;
#Autowired
WebApplicationContext wac;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
#Configuration
#EnableWebMvc
public static class TestConfiguration { }
#Controller
#RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {
#GetMapping(value = "/exception")
public #ResponseBody String find() throws Exception {
throw new Exception("global_error_test");
}
}
#Test
public void testHandleException() throws Exception {
mockMvc.perform(get("/tests/exception"))
.andExpect(new ResultMatcher() {
#Override
public void match(MvcResult result) throws Exception {
result.getResponse().getContentAsString().contains("global_error_test");
}
})
.andExpect(status().isBadRequest());
}
/*
* #Test public void testErrorDetailsValue() {
*
* thrown.expect(Exception.class); thrown.expect(IOException.class);
* thrown.expect(ParseException.class);
* thrown.expect(JsonProcessingException.class);
*
* thrown.expectMessage("Bad Request");
*
* }
*/
}
I only have a little knowledge concerning custom exceptions. What am I missing here? Thanks for any assistance.
I found out how to cover my custom exception. I just included a test on my controller that will fail the endpoint and it did catch an exception and covered my custom exception.

How can I test a validator in a Spring Boot app?

I have a class:
#Component
public class ContractorFormValidator implements Validator {
Logger logger = LoggerFactory.getLogger(ContractorFormValidator.class);
#Inject IBusinessDataValidator businessDataValidator;
#Override
public boolean supports(Class<?> clazz) {
return Contractor.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Contractor contractor = (Contractor) target;
if (!businessDataValidator.isNipValid(contractor.getContractorData().getNip())) {
errors.rejectValue("contractorData.nip", "invalid");
}
if (!businessDataValidator.isRegonValid(contractor.getContractorData().getRegon())) {
errors.rejectValue("contractorData.regon", "invalid");
}
}
}
How can I test it? I have tried this: How to test validation annotations of a class using JUnit? but this doesn't work cause the validate method in my validator requires Errors class passed to it's method signature.
I have no Idea if I can pass this Errors object to the validator. Is there any other way?
Have you tried to write a simple unit test for this?
#RunWith(SpringJUnit4ClassRunner.class)
public class ContractorFormValidatorTest {
#Autowired
private ContractorFormValidator validator;
#Test
public void testValidation() throws Exception {
Contractor contractor = new Contractor();
// Initialise the variables here.
Errors errors = new BeanPropertyBindingResult(contractor, "contractor");
validator.validate(contract, errors);
// If errors are expected.
Assert.assertTrue(errors.hasErrors());
for (Error fieldError : errors.getFieldErrors()) {
Assert.assertEquals("contractorData.nip", fieldError.getCode());
}
}
}
If you are going to use the validator in a controller implementation, then you need to use the MockMvc apis
Your set up can be included in the class above.
private MockMvc mockMvc
#Autowired
private MyController controller;
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(this.controller).build();
}
#Test
public void testMethod() {
MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.post("/yoururl")).
andExpect(MockMvcResultMatchers.status().isCreated()).andReturn();
}
Use the class org.springframework.validation.BeanPropertyBindingResult,
Errors newErrors = new BeanPropertyBindingResult(validateObject, "objectName");

How to use an interface property in my RestController to return data in my request handlers

I have declared an interface as a property of my #RestController. That interface just has a few fields/setters/getters.
The implementation of the RestController and GetMapping look like this:
#RestController
#EnableAutoConfiguration
public class AccountController {
private AccountStoreInterface store;
#GetMapping(value="/account")
public Account readAccount(#RequestParam("id") String id) throws AccountNotFoundException {
Account a = store.getAccount(id);
if (a.getId().isEmpty()) {
throw new AccountNotFoundException();
}
return a;
}
#ExceptionHandler(AccountNotFoundException.class)
#ResponseStatus(NOT_FOUND)
public #ResponseBody String handleAccountNotFoundException(AccountNotFoundException ex) {
return ex.getMessage(); }
#ExceptionHandler(NullPointerException.class)
#ResponseStatus(INTERNAL_SERVER_ERROR)
public #ResponseBody String handleNullPointerException(NullPointerException ex) {
return ex.getMessage();
}
}
The interface declaration looks like this:
public interface AccountStoreInterface {
public Account getAccount(String id) throws AccountNotFoundException;
public Account setAccount(String id, Account account) throws AccountConflictException;
}
I would like to test this using spring-boot-starter-test and junit4. I expect the following test to return a 500 because I have not passed any store object that implements my interface so it should throw a NullPointerException.
How do I configure my unit tests in order to test the 500 and 404 status codes?
Right now, the below test actually fails because the returned status is 200, which I don't understand how junit gets to.
#RunWith(SpringRunner.class)
#WebMvcTest(AccountController.class)
public class TestAccountController {
#Autowired private MockMvc mockMvc;
#Autowired private WebApplicationContext wac;
#MockBean
private AccountController accountController;
#Test
public void testGetAccountNotFound() throws Exception {
mockMvc.perform(get("/account?id={id}", "test-account-id-123")
.accept(APPLICATION_JSON)
.characterEncoding("UTF-8"))
.andDo(print())
.andExpect(status().isNotFound());
}
}

Categories

Resources