32

I want to assert that an exception is raised and that the server returns an 500 internal server error.

To highlight the intent a code snippet is provided:

thrown.expect(NestedServletException.class);
this.mockMvc.perform(post("/account")
            .contentType(MediaType.APPLICATION_JSON)
            .content(requestString))
            .andExpect(status().isInternalServerError());

Of course it dosen't matter if I write isInternalServerError or isOk. The test will pass regardless if an exception is thrown below the throw.except statement.

How would you go about to solve this?

5 Answers5

23

If you have an exception handler and you want to test for a specific exception, you could also assert that the instance is valid in the resolved exception.

.andExpect(result -> assertTrue(result.getResolvedException() instanceof WhateverException))

UPDATE (gavenkoa) Don't forget to inject @ExceptionHandler annotated methods to the test context or exception will occur at .perform() instead of capturing it with .andExpect(), basically register:

@ControllerAdvice
public class MyExceptionHandlers {
    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> handle(BindException ex) { ... }
}

with @Import(value=...) or @ContextConfiguration(classes=...) or by other means.

gavenkoa
  • 45,285
  • 19
  • 251
  • 303
Oluwatobiloba
  • 297
  • 3
  • 12
  • 1
    Really nice one!! Really clean and neat! Saved me a lifetime of trouble :) – PlickPlick Jul 08 '20 at 17:59
  • 5
    To be more specific in order to use `MvcResult.perform(...).andExpect()` where there is an exception in `@Controller` you need to inject `@ExceptionHandler` into your Spring Test Context or it throws exception right in `.perform()` just before `.andExpect()` )) Thx mate! – gavenkoa Jul 15 '20 at 08:02
  • @gavenkoa can you explain what you mean by _or it throws exception right in .perform() just before .andExpect() )_ ? I have a void method stubbed and wherein the service is annotated with @ MockBean and it is meant to throw and error but everycall to the api endpoint results in 200 instead of the exception return. – Dark Star1 Nov 14 '22 at 12:51
14

You can get a reference to the MvcResult and the possibly resolved exception and check with general JUnit assertions...

MvcResult result = this.mvc.perform(
        post("/api/some/endpoint")
                .contentType(TestUtil.APPLICATION_JSON_UTF8)
                .content(TestUtil.convertObjectToJsonBytes(someObject)))
        .andDo(print())
        .andExpect(status().is4xxClientError())
        .andReturn();

Optional<SomeException> someException = Optional.ofNullable((SomeException) result.getResolvedException());

someException.ifPresent( (se) -> assertThat(se, is(notNullValue())));
someException.ifPresent( (se) -> assertThat(se, is(instanceOf(SomeException.class))));
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Edward J Beckett
  • 5,061
  • 1
  • 41
  • 41
  • 1
    If you expect the exception in the test, you can just use `assertTrue(result.getResolvedException() instanceof SomeException)` – Paulo Mateus Jul 01 '20 at 15:27
  • 1
    This seems very confusing. You expect an exception, but use `Optional.ifPresent()`. This would at first glance suggest that `someException` could be missing, and the assertions would just be ignored. – syydi Apr 21 '22 at 10:10
  • Both valid points.... I was working with Optional> a lot back then so it may have swayed my design style a bit – Edward J Beckett Dec 11 '22 at 10:22
8

You can try something as below -

  1. Create a custom matcher

    public class CustomExceptionMatcher extends
    TypeSafeMatcher<CustomException> {
    
    private String actual;
    private String expected;
    
    private CustomExceptionMatcher (String expected) {
        this.expected = expected;
    }
    
    public static CustomExceptionMatcher assertSomeThing(String expected) {
        return new CustomExceptionMatcher (expected);
    }
    
    @Override
    protected boolean matchesSafely(CustomException exception) {
        actual = exception.getSomeInformation();
        return actual.equals(expected);
    }
    
    @Override
    public void describeTo(Description desc) {
        desc.appendText("Actual =").appendValue(actual)
            .appendText(" Expected =").appendValue(
                    expected);
    
    }
    }
    
  2. Declare a @Rule in JUnit class as below -

    @Rule
    public ExpectedException exception = ExpectedException.none();
    
  3. Use the Custom matcher in test case as -

    exception.expect(CustomException.class);
    exception.expect(CustomException
            .assertSomeThing("Some assertion text"));
    this.mockMvc.perform(post("/account")
        .contentType(MediaType.APPLICATION_JSON)
        .content(requestString))
        .andExpect(status().isInternalServerError());
    

P.S.: I have provided a generic pseudo code which you can customize as per your requirement.

Bond - Java Bond
  • 3,972
  • 6
  • 36
  • 59
0

I recently encountered the same error, and instead of using MockMVC I created an integration test as follows:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = { MyTestConfiguration.class })
public class MyTest {
    
    @Autowired
    private TestRestTemplate testRestTemplate;
    
    @Test
    public void myTest() throws Exception {
        
        ResponseEntity<String> response = testRestTemplate.getForEntity("/test", String.class);
        
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode(), "unexpected status code");
        
    }   
}

and

@Configuration
@EnableAutoConfiguration(exclude = NotDesiredConfiguration.class)
public class MyTestConfiguration {
    
    @RestController
    public class TestController {
        
        @GetMapping("/test")
        public ResponseEntity<String> get() throws Exception{
            throw new Exception("not nice");
        }           
    }   
}

this post was very helpful: https://github.com/spring-projects/spring-boot/issues/7321

jmhostalet
  • 4,399
  • 4
  • 38
  • 47
-7

In your controller:

throw new Exception("Athlete with same username already exists...");

In your test:

    try {
        mockMvc.perform(post("/api/athlete").contentType(contentType).
                content(TestUtil.convertObjectToJsonBytes(wAthleteFTP)))
                .andExpect(status().isInternalServerError())
                .andExpect(content().string("Athlete with same username already exists..."))
                .andDo(print());
    } catch (Exception e){
        //sink it
    }
Erick Audet
  • 349
  • 5
  • 13