Spring项目中如何Mock Principal对象进行订单创建方法测试?
我正在学习Spring,开发一个在线书店项目,包含图书下单功能。测试订单创建方法时,该方法通过Principal对象识别请求发起者,不知道如何Mock Principal对象完成测试,目前测试报错。
错误日志
17:52:42.111 [main] ERROR com.example.BookStoreProject.service.OrderServiceImpl -- No value present java.lang.RuntimeException: Order creation exception at com.example.BookStoreProject.service.OrderServiceImpl.createOrder(OrderServiceImpl.java:45) at com.example.BookStoreProject.service.OrderServiceImplTest.canCreateOrder(OrderServiceImplTest.java:67) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:578) 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:57) 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)
测试类代码
@ExtendWith(MockitoExtension.class) class OrderServiceImplTest { @Mock private OrdersRepository ordersRepository; @Captor private ArgumentCaptor<ArrayList<OrderCreationDtoRequest>> captor; @Mock private UserService userService; @Mock private AuthenticationService authenticationService; @Mock private OrderDetailService orderDetailService; @Mock private Principal principal; private OrderServiceImpl undertest; @InjectMocks private Orders order = new Orders(); @BeforeEach void setUp() { undertest = new OrderServiceImpl(ordersRepository,userService,orderDetailService); } @Test void canCreateOrder(){ List<OrderCreationDtoRequest> requests = new ArrayList<>(); order.setAddress("abc"); order.setCreatedAt(LocalDateTime.now()); undertest.createOrder(requests,principal); ArgumentCaptor<Orders> ordersArgumentCaptor = ArgumentCaptor.forClass(Orders.class); verify(orderDetailService).create(captor.capture(),ordersArgumentCaptor.capture()); Orders capturedOrder = ordersArgumentCaptor.getValue(); List<OrderCreationDtoRequest> capturedRequest = captor.getValue(); assertThat(capturedOrder).isEqualTo(order); assertThat(capturedRequest).isEqualTo(capturedRequest); } }
待测试类代码
public OrderCreationDtoResponse createOrder(List<OrderCreationDtoRequest> requests, Principal principal) { Orders order = new Orders(); try { LocalDateTime createdAt = LocalDateTime.now(); String email = principal.getName(); Users user = userService.getByUserEmail(email).orElseThrow(); String address = requests.get(0).getAddress(); order.setCreatedAt(createdAt); order.setUser(user); order.setAddress(address); orderDetailService.create(requests,order); }catch (Exception e){ log.error(e.getMessage()); throw new RuntimeException("Order creation exception"); } return OrderCreationDtoResponse.builder() .totalPrice(orderDetailService.totalPrice(requests)) .build(); }
问题分析与修复步骤
1. 未Mock Principal的getName()方法
已Mock Principal对象,但未指定principal.getName()的返回值,调用时返回null,导致userService.getByUserEmail(null)返回空Optional,触发orElseThrow()抛出NoSuchElementException(日志中的"No value present"即为此异常消息)。
修复:在测试方法中添加Mock逻辑:
when(principal.getName()).thenReturn("test@example.com");
2. 未Mock UserService的返回值
即使Principal返回邮箱,userService.getByUserEmail(email)默认返回空Optional,同样会触发orElseThrow()。需要Mock该方法返回有效Users对象:
Users testUser = new Users(); // 可按需设置用户属性,如ID等 when(userService.getByUserEmail("test@example.com")).thenReturn(Optional.of(testUser));
3. 请求列表为空导致索引越界
测试中requests是空ArrayList,待测试方法调用requests.get(0).getAddress()会抛出IndexOutOfBoundsException。需构造至少一个请求对象加入列表:
OrderCreationDtoRequest requestDto = new OrderCreationDtoRequest(); requestDto.setAddress("abc"); requests.add(requestDto);
4. @InjectMocks误用
给Orders order添加@InjectMocks属于误用,该对象并非测试类依赖的服务,待测试方法会自行创建Orders对象,应移除该注解。
5. 断言逻辑无效
最后一行assertThat(capturedRequest).isEqualTo(capturedRequest)永远为true,无测试意义,应改为断言捕获的请求与传入的requests相等:
assertThat(capturedRequest).isEqualTo(requests);
修复后的完整测试方法
@Test void canCreateOrder(){ // 构造测试数据 OrderCreationDtoRequest requestDto = new OrderCreationDtoRequest(); requestDto.setAddress("abc"); List<OrderCreationDtoRequest> requests = new ArrayList<>(); requests.add(requestDto); // Mock依赖方法 String testEmail = "test@example.com"; when(principal.getName()).thenReturn(testEmail); Users testUser = new Users(); when(userService.getByUserEmail(testEmail)).thenReturn(Optional.of(testUser)); // 调用待测试方法 undertest.createOrder(requests,principal); // 验证与断言 ArgumentCaptor<Orders> ordersArgumentCaptor = ArgumentCaptor.forClass(Orders.class); verify(orderDetailService).create(captor.capture(),ordersArgumentCaptor.capture()); Orders capturedOrder = ordersArgumentCaptor.getValue(); List<OrderCreationDtoRequest> capturedRequest = captor.getValue(); assertThat(capturedOrder.getAddress()).isEqualTo("abc"); assertThat(capturedOrder.getUser()).isEqualTo(testUser); assertThat(capturedRequest).isEqualTo(requests); }
内容的提问来源于stack exchange,提问作者Kai7

