1

I currently have two ControllerAdvice in my application, I'm supposed to merge them into one. But I need to test them before and after the merge, test the exception and the object that the controller return me.

I'm trying to make a jUnit test with Mockito but it seems impossible to test the exceptions without any context, without a controller, etc ...

Does anyone know how can I proceed to achieve what I'm trying to do ?

I also try to throw manually an exception but obviously it wasn't catched by the ControllerAdvice.

So basically here is what i'm trying to do: Manually throw an exception This exception is handled by my ControllerAdvice Check the returned object (code & message)

Here is a sample of code I have:

@Before
public void setup() {
  ...
    mockMvc = MockMvcBuilders.standaloneSetup(getController())
                .setControllerAdvice(new GlobalControllerExceptionHandler())
                .setCustomArgumentResolvers(resolver, resolver_0, resolver_1)
                .setHandlerExceptionResolvers(exceptionResolver).build();
}


@Controller
@RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {

    @RequestMapping(value = "/exception", method = GET)
    public @ResponseBody String find() {
        throw new EntityNotFoundException();
    }
}

@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().isNotFound());
}

I have the good status code at the end but it doesn't use my ControllerAdvice (I try with the debugger)

Spialdor
  • 1,475
  • 5
  • 20
  • 46

2 Answers2

3

You can just call handler method directly

@ControllerAdvice

MyAdvice{

    @ExceptionHandeler(listOfExxcetpions)
    public ResponseEntity someOfMyExceptionsHandler(Exception e){
       .....
     }  

}

and in test

MuTest{
private MyAdvice advice=new MyAdvice();

@Test
public void oneOfTests(){
    Exception e=new SomeSortOfExceptionToTest();
    resp=advice.someOfMyExceptionsHandler(e)
    assertThat(resp).....dostuff;
}

}

If you want to test how spring integrates with your handlers - if your annotations are correct, ordering serialization etc - well that will be an integration test and you have to boot up test context - then you can throw exceptions directly from controller methods.

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
0

Best working solution on my case under Java17 was to break your provided code into 2 steps:

  1. Create a test RestController as a separate file under tests.
  2. Create the code for the actual tests, bind with your ControllerAdvice, perhaps skip OAuth if using any, etc.

Code follows:

Test Controller (under src/test/java/.../package/controller)

@RestController
@RequestMapping("/tests/exception")
public class RestProcessingExceptionThrowingController {
    @GetMapping(value = "/bad-user-input")
    public @ResponseBody String find() {
        throw new BadUserInputException("global_exception_handler_test_bad_user_input");
    }
}

Test's code (under src/test/java/.../package/)

@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
public class GlobalControllerExceptionHandlerTestIT {
    @Autowired
    private MockMvc mockMvc;
    @InjectMocks
    private RestProcessingExceptionThrowingController restProcessingExceptionThrowingController;
    /**
     * Bypass security, optional step.
     */
    @Configuration
    @EnableWebSecurity
    public static class TestSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().permitAll();
        }
    }
    @BeforeEach
    public void setup() {
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(restProcessingExceptionThrowingController)     // instantiate controller.
                .setControllerAdvice(new GlobalControllerExceptionHandler())   // bind with controller advice.
                .build();
    }
    @Test
    public void testExceptionThrowingControllerExists() {
        assertThat(restProcessingExceptionThrowingController).isNotNull();
    }
    /**
     * Tests that BadUserInputException is handled via global exception handler.
     *
     * @throws Exception
     */
    @Test
    public void testHandleBadUserInputException() throws Exception {
        this.mockMvc.perform(get("/tests/exception/bad-user-input"))
                .andExpect(status().isNotAcceptable()) //HTTP 406, what my global exc. controller does.
                .andExpect(result -> Assertions.assertTrue(result.getResolvedException() instanceof BadUserInputException))
                .andExpect(result -> Assertions.assertEquals("global_exception_handler_test_bad_user_input",
                        Objects.requireNonNull(result.getResolvedException()).getMessage()));
}

P.S: Code assumes that your GlobalControllerExceptionHandler.java handles/wraps/catches a BadUserInputException

apfel
  • 31
  • 3