You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockito与JUnit5测试Service的update方法抛异常,求正确测试方案

问题:Mockito+JUnit5测试CategoryService的update方法时触发异常

测试update方法时,报错指向CategoryService中的.orElseThrow(() -> new MyException(ErrorTable.CATEGORY_NOT_FOUND))行,以下是相关代码及堆栈信息:

CategoryService代码

public Category update(Long id, CategoryDto categoryDto) {

        Category category = categoryRepository.findById(id)
                        .orElseThrow(() -> new MyException(ErrorTable.CATEGORY_NOT_FOUND));
        
        mapper.map(categoryDto, category);

        return categoryRepository.save(category);
}

CategoryServiceTest代码

private static final Long ID = 1L;
private static final String NAME = "horror";

@InjectMocks
private CategoryService service;

@Mock
private CategoryRepository repository;

@Mock
private ModelMapper mapper;

@BeforeEach
void setup() {
    MockitoAnnotations.openMocks(this);
    startCategory();
}


@Test
void whenUpdateCategoryThanReturnSuccess() {
        
    when(repository.save(any())).thenReturn(category);

    Category response = service.update(ID, categoryDto);

    assertNotNull(response);
    assertEquals(Category.class, response.getClass());
    assertEquals(ID, response.getId());
    assertEquals(NAME, response.getName());
}

private void startCategory() {

    category = new Category();
    category.setId(ID);
    category.setName(NAME);

    categoryDto = new CategoryDto();
    categoryDto.setName(NAME);

}

堆栈信息

com.exceptions.MyException
    at com.services.CategoryService.lambda$2(CategoryService.java:79)
    at java.util.Optional.orElseThrow(Optional.java:290) 
    at com.services.CategoryService.update(CategoryService.java:79)
    at com.test.services.CategoryServiceTest.whenUpdateCategoryThanReturnSuccess(CategoryServiceTest.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
    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:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    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.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
    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.util.ArrayList.forEach(ArrayList.java:1257)
    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.util.ArrayList.forEach(ArrayList.java:1257)
    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:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
解决方案

问题根源

测试代码仅mock了repository.save()的返回,但未mockrepository.findById(ID)的行为。Mockito默认会返回空的Optional,导致执行到orElseThrow时直接抛出MyException。

修改后的完整测试代码

private static final Long ID = 1L;
private static final String NAME = "horror";
private static final Long NON_EXISTENT_ID = 999L;

@InjectMocks
private CategoryService service;

@Mock
private CategoryRepository repository;

@Mock
private ModelMapper mapper;

private Category category;
private CategoryDto categoryDto;

@BeforeEach
void setup() {
    MockitoAnnotations.openMocks(this);
    startCategory();
}


@Test
void whenUpdateCategoryThanReturnSuccess() {
    // Mock findById返回存在的分类实例
    when(repository.findById(ID)).thenReturn(Optional.of(category));
    // Mock save返回更新后的分类
    when(repository.save(any(Category.class))).thenReturn(category);
    // Mock mapper的map方法(void返回类型需用doNothing)
    doNothing().when(mapper).map(categoryDto, category);

    Category response = service.update(ID, categoryDto);

    assertNotNull(response);
    assertEquals(Category.class, response.getClass());
    assertEquals(ID, response.getId());
    assertEquals(NAME, response.getName());
    // 验证依赖方法的调用次数
    verify(repository, times(1)).findById(ID);
    verify(repository, times(1)).save(category);
    verify(mapper, times(1)).map(categoryDto, category);
}

// 补充测试:更新不存在的分类时抛出异常
@Test
void whenUpdateNonExistentCategoryThanThrowException() {
    when(repository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty());

    MyException exception = assertThrows(MyException.class, () -> {
        service.update(NON_EXISTENT_ID, categoryDto);
    });

    assertEquals(ErrorTable.CATEGORY_NOT_FOUND, exception.getError());
    verify(repository, times(1)).findById(NON_EXISTENT_ID);
    // 确保save方法不会被调用
    verify(repository, never()).save(any(Category.class));
}

private void startCategory() {
    category = new Category();
    category.setId(ID);
    category.setName(NAME);

    categoryDto = new CategoryDto();
    categoryDto.setName(NAME);
}

核心修改点

  1. 新增when(repository.findById(ID)).thenReturn(Optional.of(category)),模拟数据库中存在目标ID的分类,避免触发异常。
  2. 显式mockModelMapper的map方法:由于该方法是void返回类型,用doNothing()声明更清晰(Mockito默认也会空执行,但显式声明更符合测试可读性要求)。
  3. 添加方法调用验证,确保依赖组件的方法都按预期执行。
  4. 新增异常场景测试,覆盖分类不存在的分支,保证测试逻辑的完整性。

内容的提问来源于stack exchange,提问作者BrookTheSinger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 05:30:52