You need to enable JavaScript to run this app. Spring Boot测试类中getLogo()多测试同跑失败、单独运行通过的问题
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot测试类中getLogo()多测试同跑失败、单独运行通过的问题

问题根因

你碰到的问题本质是测试用例之间没有做状态隔离,存在隐式的执行依赖:

  1. JUnit4 默认按测试方法名称的字典序执行,顺序为deleteLogo() → getLogo() → uploadLogo()。没有任何前置状态准备的情况下,先执行的deleteLogo()会把环境里提前存在的logo文件、数据库配置记录清空。等轮到执行getLogo()时,因为之前的清理操作,已经找不到对应的logo文件了。
  2. 生产代码里的getLogo方法有逻辑漏洞:getResourceAsStream()在找不到指定路径的资源时,不会抛出IOException,而是直接返回null。你把这个null直接传给IOUtils.toByteArray()时,就会抛出NullPointerException——这个异常属于运行时异常,根本触发不了原来写的catch块(catch仅捕获IOException),会直接抛到Web层形成你看到的嵌套异常,并不是你之前以为的走进了catch块。
  3. 单独运行getLogo()测试时,不会触发前面的deleteLogo()的清理操作,类路径下提前放好的测试文件、数据库初始化的配置记录都还在,所以能正常运行通过。

调整步骤

1. 先修复生产代码的空指针隐患

把ConfigPropertiesService的getLogo方法加上非空判断,同时补全资源自动关闭逻辑:

public byte[] getLogo() {
    ConfigPropertiesEntity configPropertiesEntity = configPropertiesRepository.findByName("com.example.logoName");
    if (configPropertiesEntity == null) {
        return null;
    }
    String logoRelativePath = "/static/photos/" + configPropertiesEntity.getValue();
    // 用try-with-resources自动关闭输入流
    try (InputStream in = getClass().getResourceAsStream(logoRelativePath)) {
        // 资源不存在时直接返回null,避免后续NPE
        if (in == null) {
            return null;
        }
        return IOUtils.toByteArray(in);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

2. 给测试类加状态隔离,避免用例互相影响

  • 加@Transactional注解,让测试过程中的数据库操作自动回滚,不会残留数据影响其他用例。
  • 写统一的@Before前置清理逻辑,每个测试方法执行前,清空数据库里的logo相关记录、删掉测试目录下的残留文件,把环境重置到已知的初始状态。
  • 给每个测试方法准备自己需要的前置数据,不要依赖环境残留的状态。

调整后的测试类参考:

@WebAppConfiguration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ReceiptApplication.class})
@Transactional // 数据库操作自动回滚
public class PropertiesServiceTest {

    @Autowired
    private ConfigPropertiesService configPropertiesService;
    @Autowired
    private ConfigPropertiesRepository configPropertiesRepository;
    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mockMvc;

    @Before
    public void setup() throws Exception {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
        // 前置清理:删掉数据库里的logo配置
        configPropertiesRepository.deleteByName("com.example.logoName");
        // 前置清理:删掉照片目录下的残留测试文件
        File photosDir = new File("target/classes/static/photos");
        if (photosDir.exists() && photosDir.listFiles() != null) {
            for (File file : photosDir.listFiles()) {
                file.delete();
            }
        }
    }

    @Test
    public void uploadLogo() throws Exception {
        MockMultipartFile file = new MockMultipartFile("logo", "originalMock.jpg", null, "bar".getBytes());
        mockMvc.perform(multipart("/settings/uploadLogo").file(file))
                .andExpect(status().isOk());
    }

    @Test
    public void getLogo() throws Exception {
        // 自己准备前置数据:插入logo配置、生成测试文件
        ConfigPropertiesEntity testConfig = new ConfigPropertiesEntity();
        testConfig.setName("com.example.logoName");
        testConfig.setValue("test-logo.jpg");
        configPropertiesRepository.save(testConfig);
        // 生成对应测试文件
        File testFile = new File("target/classes/static/photos/test-logo.jpg");
        testFile.getParentFile().mkdirs();
        FileUtils.writeByteArrayToFile(testFile, "test-content".getBytes());

        mockMvc.perform(get("/settings/getLogo"))
                .andExpect(status().isOk());
    }

    @Test
    public void deleteLogo() throws Exception {
        // 先准备前置数据:上传一个logo,再执行删除测试
        MockMultipartFile file = new MockMultipartFile("logo", "delete-test.jpg", null, "bar".getBytes());
        mockMvc.perform(multipart("/settings/uploadLogo").file(file));
        // 执行删除接口
        mockMvc.perform(delete("/settings/deleteLogo"))
                .andExpect(status().isOk());
    }
}

3. 注意事项

不要依赖默认的测试执行顺序来做测试,一旦顺序变动就很容易出现类似的环境残留问题。如果确实需要固定执行顺序,可以用JUnit的@FixMethodOrder(MethodSorters.JVM)注解指定,但优先推荐做每个用例的独立状态准备,从根源上避免用例耦合。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.02 08:36:03