如何测试返回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
相关产品推荐
相关产品推荐

