如何用JUnit和Mockito模拟Azure文件存储上传服务避免真实调用
问题分析与解决方案
核心问题
- Mock对象未被使用:原服务类中直接通过
CloudStorageAccount.parse(CONNECTION_STRING)生成实例,测试里的CloudStorageAccountmock完全没参与实际逻辑,导致验证时提示"无交互"。 - 测试顺序错误:原测试先调用
uploadFile,后设置Mock Stub,导致Stub无法生效。 - 硬编码配置:服务类里的连接字符串、共享名等是静态常量,无法在测试中替换,导致验证参数不匹配。
- 覆盖率不足:未覆盖异常分支,导致覆盖率仅87%。
第一步:重构ReportUploadService
将硬编码配置移至AzureFileProperties,抽离存储账户创建逻辑为可替换的方法,方便测试Mock:
@Service @Log4j2 public class ReportUploadService { private final AzureFileProperties azureFileProperties; @Autowired public ReportUploadService(AzureFileProperties azureFileProperties) { this.azureFileProperties = azureFileProperties; } public void uploadFile(String filePath) throws FileNotFoundException { try { CloudStorageAccount storageAccount = getCloudStorageAccount(); CloudFileClient fileClient = storageAccount.createCloudFileClient(); CloudFileShare share = fileClient.getShareReference(azureFileProperties.getShareName()); CloudFileDirectory rootDir = share.getRootDirectoryReference(); CloudFileDirectory sampleDir = rootDir.getDirectoryReference(azureFileProperties.getDirName()); CloudFile cloudFile = sampleDir.getFileReference("testfile.txt"); cloudFile.uploadFromFile(filePath); } catch (Exception ex) { log.error("File could not be uploaded", ex); throw new FileNotFoundException(ex.getMessage()); } } // 抽为protected方法,测试时可通过Spy替换 protected CloudStorageAccount getCloudStorageAccount() throws InvalidKeyException, URISyntaxException { return CloudStorageAccount.parse(azureFileProperties.getConnectionString()); } }
对应的AzureFileProperties配置类:
@ConfigurationProperties(prefix = "azure.file") public class AzureFileProperties { private String connectionString; private String shareName; private String dirName; // 生成getter、setter }
第二步:修复测试类
使用Spy替换存储账户创建逻辑,调整Stub顺序,添加异常分支测试:
@ExtendWith(MockitoExtension.class) class ReportUploadServiceTest { @InjectMocks private ReportUploadService uploadService; @Mock private AzureFileProperties azureFileProperties; @Mock private CloudStorageAccount cloudStorageAccount; @Mock private CloudFileClient cloudFileClient; @Mock private CloudFileDirectory rootDir; @Mock private CloudFileDirectory sampleDir; @Mock private CloudFileShare cloudFileShare; @Mock private CloudFile cloudFile; @BeforeEach void setUp() { // 配置Mock配置类返回值 when(azureFileProperties.getConnectionString()).thenReturn("mock-connection"); when(azureFileProperties.getShareName()).thenReturn("abc"); when(azureFileProperties.getDirName()).thenReturn("test"); // 创建Spy对象,替换存储账户创建方法 uploadService = spy(uploadService); doReturn(cloudStorageAccount).when(uploadService).getCloudStorageAccount(); } @Test void uploadFile_success() throws Exception { // 提前设置所有Mock Stub when(cloudStorageAccount.createCloudFileClient()).thenReturn(cloudFileClient); when(cloudFileClient.getShareReference("abc")).thenReturn(cloudFileShare); when(cloudFileShare.getRootDirectoryReference()).thenReturn(rootDir); when(rootDir.getDirectoryReference("test")).thenReturn(sampleDir); when(sampleDir.getFileReference("testfile.txt")).thenReturn(cloudFile); doNothing().when(cloudFile).uploadFromFile(anyString()); // 调用测试方法 File testFile = new File("src/test/resources/testfile.txt"); uploadService.uploadFile(testFile.getAbsolutePath()); // 验证交互 verify(cloudStorageAccount).createCloudFileClient(); verify(cloudFileClient).getShareReference("abc"); verify(cloudFileShare).getRootDirectoryReference(); verify(rootDir).getDirectoryReference("test"); verify(sampleDir).getFileReference("testfile.txt"); verify(cloudFile).uploadFromFile(testFile.getAbsolutePath()); } @Test void uploadFile_throwsFileNotFoundException() throws Exception { // 模拟上传异常 when(cloudStorageAccount.createCloudFileClient()).thenReturn(cloudFileClient); when(cloudFileClient.getShareReference("abc")).thenReturn(cloudFileShare); when(cloudFileShare.getRootDirectoryReference()).thenReturn(rootDir); when(rootDir.getDirectoryReference("test")).thenReturn(sampleDir); when(sampleDir.getFileReference("testfile.txt")).thenReturn(cloudFile); doThrow(new StorageException("upload failed", null, null, null, null)) .when(cloudFile).uploadFromFile(anyString()); // 验证异常抛出 FileNotFoundException exception = assertThrows(FileNotFoundException.class, () -> uploadService.uploadFile("invalid-file-path")); assertTrue(exception.getMessage().contains("upload failed")); } }
关键修改说明
- Spy替换逻辑:通过
spy(uploadService)替换getCloudStorageAccount方法,避免调用真实的Azure存储API。 - Stub顺序调整:先设置所有Mock的返回值,再调用测试方法,确保Mock交互被捕获。
- 异常分支覆盖:新增异常测试用例,覆盖catch块逻辑,将测试覆盖率提升至100%。
- 参数匹配:验证时使用服务类实际使用的参数(如原代码中的
SHARE_NAME="abc"),避免参数不匹配导致验证失败。
内容的提问来源于stack exchange,提问作者RuchiG
相关产品推荐
相关产品推荐

