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

Spring Boot测试中无法读取application-test.properties的文件上传目录配置导致测试失败

Spring Boot测试中无法读取application-test.properties的文件上传目录配置导致测试失败

你遇到的问题核心很明确:纯Mockito测试模式下,Spring的@Value注解不会被自动处理。你当前的测试只用到了MockitoAnnotations.openMocks,这种方式不会启动Spring上下文,所以@Value("${file.upload-dir}")根本没机会从配置文件里读取值,uploadDir始终是null,自然触发了方法里的空值校验异常。而直接写死路径时,uploadDir有了有效值,方法就能正常执行。

下面给你几个可行的解决方案,你可以根据测试场景选择:

方案一:用@SpringBootTest启动完整Spring上下文(适合需要Spring环境的测试)

如果你的测试需要依赖Spring的其他特性,或者想让测试更贴近真实运行环境,可以用@SpringBootTest启动完整Spring上下文,同时用@MockBean来Mock掉不需要真实实例的依赖(比如ItemRepository和Converter)。这样Spring会自动处理@Value的配置注入。

修改后的测试类代码:

@SpringBootTest
// 如果你的测试配置文件是application-test.properties,可指定路径;不指定的话默认会读取src/test/resources下的配置
@TestPropertySource(locations = "classpath:application-test.properties")
public class ItemServiceImplTest {

    @Value("${file.upload-dir}")
    private String uploadDir;

    // 用@MockBean替代@Mock,让Spring管理这些Mock实例
    @MockBean
    private ItemRepository itemRepository;

    @MockBean
    private Converter converter;

    // 直接从Spring容器注入ItemServiceImpl实例
    @Autowired
    private ItemServiceImpl itemService;

    @Test
    void testSaveTempFile() throws IOException {
        // Arrange
        MultipartFile mockFile = mock(MultipartFile.class);
        String originalFileName = "test_image.png";
        byte[] fileContent = "Dummy Content".getBytes();

        when(mockFile.getOriginalFilename()).thenReturn(originalFileName);
        when(mockFile.getBytes()).thenReturn(fileContent);

        // Act
        String savedFileName = itemService.saveTempFile(mockFile);

        // Assert
        assertTrue(savedFileName.startsWith("TEMP_"));
        assertTrue(savedFileName.endsWith(".png"));

        Path savedFilePath = Paths.get(uploadDir, savedFileName);
        assertTrue(Files.exists(savedFilePath));
        assertArrayEquals(fileContent, Files.readAllBytes(savedFilePath));

        // 测试后清理文件
        Files.deleteIfExists(savedFilePath);
    }
}

方案二:轻量级Spring测试(@ExtendWith+@TestPropertySource)

如果你不想启动完整的应用上下文,只想让Spring处理@Value注解,可以用SpringExtension配合@TestPropertySource,这样既能读取配置,又能保持测试的轻量性:

// JUnit 5中@ExtendWith(SpringExtension.class)可以省略,因为Spring相关注解已经默认集成
@ExtendWith(SpringExtension.class)
@TestPropertySource(locations = "classpath:application-test.properties")
public class ItemServiceImplTest {

    @Value("${file.upload-dir}")
    private String uploadDir;

    @Mock
    private ItemRepository itemRepository;

    @Mock
    private Converter converter;

    @InjectMocks
    private ItemServiceImpl itemService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // 此时@Value已经被Spring处理,uploadDir已经有配置值了
    }

    // 测试逻辑和你原来的代码一致...
    @Test
    void testSaveTempFile() throws IOException {
        // 你的原有测试代码
    }
}

方案三:纯Mockito测试(手动设置uploadDir)

如果你想完全脱离Spring容器,保持纯单元测试的速度,可以手动给itemService的uploadDir字段赋值。这里有两种方式:

方式A:给ItemServiceImpl添加测试用的setter

在你的ItemServiceImpl里加一个仅用于测试的setter方法(可以用@VisibleForTesting注解标记,表明这是测试专用):

import com.google.common.annotations.VisibleForTesting;

public class ItemServiceImpl implements ItemService {
    // ... 原有代码 ...

    @VisibleForTesting
    void setUploadDir(String uploadDir) {
        this.uploadDir = uploadDir;
    }
}

然后在测试类的setUp中调用这个方法:

@BeforeEach
void setUp() throws IOException {
    MockitoAnnotations.openMocks(this);
    // 可以用系统临时目录,或者自定义测试目录
    String testUploadDir = System.getProperty("java.io.tmpdir") + "/test-uploads";
    // 确保目录存在
    Files.createDirectories(Paths.get(testUploadDir));
    // 手动设置上传目录
    itemService.setUploadDir(testUploadDir);
}

方式B:用反射设置私有字段

如果你不想修改业务代码,可以用反射直接给私有字段赋值:

@BeforeEach
void setUp() throws NoSuchFieldException, IllegalAccessException, IOException {
    MockitoAnnotations.openMocks(this);
    String testUploadDir = "/tmp/test-uploads";
    Files.createDirectories(Paths.get(testUploadDir));
    
    // 用反射获取uploadDir字段并赋值
    Field uploadDirField = ItemServiceImpl.class.getDeclaredField("uploadDir");
    uploadDirField.setAccessible(true);
    uploadDirField.set(itemService, testUploadDir);
}

最后注意事项

  1. 确保你的application-test.properties放在src/test/resources目录下,并且配置正确:
    file.upload-dir=/tmp/test-uploads
    
  2. 测试用的上传目录最好用临时目录,避免污染本地环境,测试完成后记得清理文件;
  3. 如果用JUnit 5,大部分Spring相关的扩展注解已经默认集成,不需要手动加@ExtendWith。

备注:内容来源于stack exchange,提问作者Rockio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 10:44:32