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

如何测试返回Mock对象的SpringBoot服务函数内部逻辑?

测试Spring Boot服务中store方法的属性设置验证方案

你当前的测试仅能确保store方法不抛出异常,但无法验证内部对UploadedScanQueryFile属性的赋值是否正确。以下是针对该场景的具体测试优化方案:

现有代码回顾

服务方法代码

public UploadedScanQueryFile store(MultipartFile file) {
    
    UploadedScanQueryFile newFileRecord = new UploadedScanQueryFile();
    
    newFileRecord.setUploadDateTime(LocalDateTime.now(ZoneOffset.UTC));
    try {
        newFileRecord.setFile(file.getBytes());
    } catch (IOException ex) {
        log.debug("Exception when trying to get bytes form file : {}", ex.toString());
    }
    newFileRecord.setFileName(file.getOriginalFilename());

    return fileRepository.save(newFileRecord);
}

当前测试代码

@MockBean UploadedScanQueryFileRepository fileRepository;

@Test
void doValidStoreTest() throws IOException {
    String filename = "ScanQueryUploadTestFile.csv";
    File testCSVfile = new ClassPathResource(filename).getFile();
    InputStream testCSVInputStream = new FileInputStream(testCSVfile);
    MockMultipartFile testCSVMultipartFile = new MockMultipartFile(filename, testCSVInputStream);

    UploadedScanQueryFile result = new UploadedScanQueryFile();
    result.setFileName(filename);
    result.setId(99);
    when(fileRepository.save(org.mockito.ArgumentMatchers.any(UploadedScanQueryFile.class))).thenReturn(result);

    storageService.store(testCSVMultipartFile);

    assertEquals(99, result.getId());
}

优化测试方案

1. 使用ArgumentCaptor捕获并验证save方法的参数

通过Mockito的ArgumentCaptor,可以捕获传入fileRepository.save()的UploadedScanQueryFile实例,进而验证其属性是否符合预期。

修改后的测试代码:

@MockBean UploadedScanQueryFileRepository fileRepository;
@Autowired
private StorageService storageService;

@Test
void doValidStoreTest() throws IOException {
    // 准备测试文件
    String filename = "ScanQueryUploadTestFile.csv";
    File testCSVfile = new ClassPathResource(filename).getFile();
    byte[] fileBytes = Files.readAllBytes(testCSVfile.toPath());
    MockMultipartFile testCSVMultipartFile = new MockMultipartFile(filename, fileBytes);

    // 定义ArgumentCaptor用于捕获UploadedScanQueryFile对象
    ArgumentCaptor<UploadedScanQueryFile> fileCaptor = ArgumentCaptor.forClass(UploadedScanQueryFile.class);
    
    // 预设save方法的返回值
    UploadedScanQueryFile mockResult = new UploadedScanQueryFile();
    mockResult.setFileName(filename);
    mockResult.setId(99);
    when(fileRepository.save(any(UploadedScanQueryFile.class))).thenReturn(mockResult);

    // 调用目标方法
    UploadedScanQueryFile returnedFile = storageService.store(testCSVMultipartFile);

    // 验证save方法被调用,并捕获传入的参数
    verify(fileRepository).save(fileCaptor.capture());
    UploadedScanQueryFile capturedFile = fileCaptor.getValue();

    // 验证属性设置是否正确
    assertEquals(filename, capturedFile.getFileName());
    assertArrayEquals(fileBytes, capturedFile.getFile());
    assertNotNull(capturedFile.getUploadDateTime());
    // 验证返回值与预设一致
    assertEquals(99, returnedFile.getId());
}

2. 固定时间源,消除时间断言的不确定性

原方法中使用LocalDateTime.now(ZoneOffset.UTC)生成时间,每次测试的时间都不同,无法直接断言具体值。可以通过依赖注入Clock来解决这个问题:

修改服务代码,注入Clock

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Service
public class StorageService {
    private final UploadedScanQueryFileRepository fileRepository;
    private final Clock clock;

    // 构造函数注入Clock
    public StorageService(UploadedScanQueryFileRepository fileRepository, Clock clock) {
        this.fileRepository = fileRepository;
        this.clock = clock;
    }

    public UploadedScanQueryFile store(MultipartFile file) {
        UploadedScanQueryFile newFileRecord = new UploadedScanQueryFile();
        // 使用注入的Clock获取当前时间
        newFileRecord.setUploadDateTime(LocalDateTime.now(clock));
        try {
            newFileRecord.setFile(file.getBytes());
        } catch (IOException ex) {
            log.debug("Exception when trying to get bytes form file : {}", ex.toString());
        }
        newFileRecord.setFileName(file.getOriginalFilename());

        return fileRepository.save(newFileRecord);
    }
}

修改测试代码,传入固定Clock

@MockBean UploadedScanQueryFileRepository fileRepository;
// 注入固定的Clock,指定UTC时间
@Bean
public Clock fixedClock() {
    return Clock.fixed(Instant.parse("2024-01-01T10:00:00Z"), ZoneOffset.UTC);
}

@Autowired
private StorageService storageService;
@Autowired
private Clock clock;

@Test
void doValidStoreTest() throws IOException {
    // ... 其他准备代码不变

    // 验证上传时间是否与固定Clock的时间一致
    assertEquals(LocalDateTime.now(clock), capturedFile.getUploadDateTime());
}

3. 测试IOException异常分支

模拟MultipartFile.getBytes()抛出IOException,验证服务的异常处理逻辑:

@Test
void store_WhenGetBytesThrowsIOException_ShouldLogAndSetFileToNull() throws IOException {
    // 模拟抛出IOException的MultipartFile
    MockMultipartFile faultyFile = mock(MockMultipartFile.class);
    when(faultyFile.getOriginalFilename()).thenReturn("faulty.csv");
    when(faultyFile.getBytes()).thenThrow(new IOException("Failed to read file"));

    // 执行方法
    storageService.store(faultyFile);

    // 捕获参数并验证file属性
    ArgumentCaptor<UploadedScanQueryFile> fileCaptor = ArgumentCaptor.forClass(UploadedScanQueryFile.class);
    verify(fileRepository).save(fileCaptor.capture());
    assertNull(fileCaptor.getValue().getFile());
    
    // 可选:验证日志是否输出(需要用@SpyBean注入Logger或使用日志测试库)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:13:20