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

测试类中使用@PostConstruct导致其被多次调用的问题求助

我懂你的痛点——@WithUserDetails需要从数据库捞取用户,但测试启动时数据库里还没这个用户,肯定会直接报错对吧?我之前也碰到过一模一样的场景,给你几个实际用过的可行方案:

方案一:JUnit 4 @BeforeClass 配合 Spring 上下文创建用户

因为你的测试类是抽象类且用了 JUnit 4 的 @RunWith(SpringRunner.class),可以在抽象类里加一个静态初始化方法,手动从 Spring 上下文拿到用户相关的 Repository/Service,提前创建好测试用户:

@RunWith(value = SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "email@address.com")
public abstract class IntegrationTests {

    @Autowired
    private MockMvc mockMvc;
    
    // 静态变量存上下文,用来获取Repository实例
    private static ApplicationContext applicationContext;

    // 把Spring上下文注入到静态变量中
    @Autowired
    public void setApplicationContext(ApplicationContext context) {
        applicationContext = context;
    }

    @BeforeClass
    public static void setupTestUser() {
        UserRepository userRepository = applicationContext.getBean(UserRepository.class);
        // 先判断用户是否存在,避免重复插入
        if (userRepository.findByEmail("email@address.com") == null) {
            User testUser = new User();
            testUser.setEmail("email@address.com");
            // 注意:密码必须和Spring Security的加密规则一致,不能存明文
            PasswordEncoder encoder = applicationContext.getBean(PasswordEncoder.class);
            testUser.setPassword(encoder.encode("test123456"));
            testUser.setUsername("testUser");
            // 补充其他必要字段
            userRepository.save(testUser);
        }
    }

    // 你的测试方法...
}
方案二:自定义 TestExecutionListener 解耦初始化逻辑

如果觉得静态方法的写法有点别扭,可以自定义一个测试执行监听器,在测试类启动前自动完成用户创建,代码更解耦:

首先写一个自定义监听器:

public class TestUserSetupListener extends AbstractTestExecutionListener {

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        super.beforeTestClass(testContext);
        ApplicationContext context = testContext.getApplicationContext();
        UserRepository userRepository = context.getBean(UserRepository.class);
        
        if (userRepository.findByEmail("email@address.com") == null) {
            User testUser = new User();
            testUser.setEmail("email@address.com");
            PasswordEncoder encoder = context.getBean(PasswordEncoder.class);
            testUser.setPassword(encoder.encode("test123456"));
            testUser.setUsername("testUser");
            userRepository.save(testUser);
        }
    }
}

然后在你的抽象测试类上绑定这个监听器:

@RunWith(value = SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "email@address.com")
// 关键:MERGE_WITH_DEFAULTS 会保留Spring默认的监听器,避免功能失效
@TestExecutionListeners(listeners = TestUserSetupListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public abstract class IntegrationTests {

    @Autowired
    private MockMvc mockMvc;

    // 你的测试方法...
}
额外建议:测试后清理数据

不管用哪种方案,都建议在测试结束后清理测试用户,避免污染数据库。比如在方案一中添加:

@AfterClass
public static void cleanupTestUser() {
    UserRepository userRepository = applicationContext.getBean(UserRepository.class);
    User testUser = userRepository.findByEmail("email@address.com");
    if (testUser != null) {
        userRepository.delete(testUser);
    }
}

内容的提问来源于stack exchange,提问作者matthew.kempson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:40:06