I am having trouble to test a controller class with @ExceptionHandler annotation.
At first, my controller was like this :
@RestController
@RequestMapping("/api/user")
@Tag(name = "User")
public class UserRestController {
private final UserService userService;
@Autowired
public UserRestController(UserService userService){
this.userService = userService;
}
@PutMapping("/update")
@Operation(summary = "Update a User",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "User to update",
required = true,
content = @Content(schema = @Schema(implementation = User.class))
))
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Update Ok",
content = { @Content(schema = @Schema(implementation = User.class))}),
@ApiResponse(responseCode = "404", description = "Ressource not found",
content = @Content),
@ApiResponse(responseCode = "400", description = "Invalid input",
content = @Content),
@ApiResponse(responseCode = "500", description = "Internal server error",
content = @Content),
}
)
public ResponseEntity<User> updateUser (@RequestBody User user) {
try {
userService.getUserById(user.getIdUser());
User newUser = userService.updateUser(user);
return ResponseEntity.status(HttpStatus.OK).body(newUser);
} catch (NotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
And my test :
@SpringBootTest(classes = MyApp.class)
@ExtendWith(MockitoExtension.class)
class UserRestControllerTest {
@Autowired
private UserRestController userController;
@MockBean
private UserService userService;
private User user1;
private User user2;
@BeforeEach
public void setUp() {
user1 = new User();
user1.setIdUser(1L);
user2 = new User();
user2.setIdUser(2L);
}
@Test
void testUpdateUserInternalServerError() {
Mockito.when(userService.updateUser(user1)).thenThrow(new RuntimeException());
ResponseEntity<User> response = userController.updateUser(user1);
Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
}
Here everything worked just fine, but I had a lot of duplication in my controller due to several methods with try/catch blocks, so I modified it to have :
@RestController
@RequestMapping("/api/user")
@Tag(name = "User")
public class UserRestController {
private final UserService userService;
@Autowired
public UserRestController(UserService userService){
this.userService = userService;
}
@PutMapping("/update")
@Operation(summary = "Update a User",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "User to update",
required = true,
content = @Content(schema = @Schema(implementation = User.class))
))
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "Update Ok",
content = { @Content(schema = @Schema(implementation = User.class))}),
@ApiResponse(responseCode = "404", description = "Ressource not found",
content = @Content),
@ApiResponse(responseCode = "400", description = "Invalid input",
content = @Content),
@ApiResponse(responseCode = "500", description = "Internal server error",
content = @Content),
}
)
public ResponseEntity<User> updateUser (@RequestBody User user) {
userService.getUserById(user.getIdUser());
User newUser = userService.updateUser(user);
return ResponseEntity.status(HttpStatus.OK).body(newUser);
}
@ExceptionHandler(value = Exception.class)
public ResponseEntity handleException(Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
When I start my application, everything is good, the exceptionHandler do its job and send a 500 response to my front app. The problem is when I launch my test class I have an error :
10:55:49.308 [main] DEBUG o.s.t.c.s.DirtiesContextTestExecutionListener - After test method: class [UserRestControllerTest], method [testUpdateUserInternalServerError], class annotated with @DirtiesContext [false] with mode [null], method annotated with @DirtiesContext [false] with mode [null]
10:55:49.308 [main] DEBUG o.s.t.c.w.ServletTestExecutionListener - Resetting RequestContextHolder for test class com.open.myApp.back.controllers.UserRestControllerTest
java.lang.RuntimeException
at com.open.myApp.back.services.UserServiceImpl.updateUser(UserServiceImpl.java:49)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704)
at com.open.myApp.back.services.UserServiceImpl$$SpringCGLIB$$0.updateUser(<generated>)
at com.open.myApp.back.controllers.UserRestController.updateUser(UserRestController.java:81)
at com.open.myApp.back.controllers.UserRestControllerTest.testUpdateUserInternalServerError(UserRestControllerTest.java:112)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Does anyone have an idea on how to deal with that ?
Thanks for your Help.
I tried to mock the response of my repo instead of the one of my service, but the problem was still there.
Problem solved!
Thanks to the explanations of M. Deinum (see comments), I figured out what was the deal. Here is my class test using MockMvc :
@SpringBootTest(classes = MyApp.class)
@AutoConfigureJsonTesters
@AutoConfigureMockMvc
class UserRestControllerTest {
@MockBean
private UserService userService;
@Autowired
private WebApplicationContext wac;
@Autowired
private JacksonTester<User> jsonUser;
private MockMvc mockMvc;
private static final String USER_1 = """
{
"idUser": "1"
}
""";
private User user1;
private User user2;
@BeforeEach
public void setUp() {
user1 = new User();
user1.setIdUser(1L);
user2 = new User();
user2.setIdUser(2L);
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
void testUpdateUserInternalServerError() throws Exception {
Mockito.when(userService.updateUser(user1)).thenThrow(new RuntimeException());
MockHttpServletResponse response = this.mockMvc.perform(put("/api/user/update")
.contentType(APPLICATION_JSON)
.content(USER_1)
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
Assertions.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
}
As per Spring docs:
In other words, you need a bit more "Spring magic" than just your own controller to be able to process an exception in a proper handler. That's why you should use something like MockMvc