Spring Boot单元测试触发主类执行的原因及阻止方法
问题原因
这事儿我做Spring Boot测试时也踩过一模一样的坑!核心原因就在你用的@SpringBootTest注解——它默认会加载完整的Spring Boot应用上下文,而你的SampleMain类实现了CommandLineRunner接口,这个接口的run方法是Spring上下文初始化完成后自动触发执行的,不管是正常启动应用还是跑单元测试,只要上下文加载完成就会触发。
再加上你的SampleMain是被@SpringBootApplication标记的主类,@SpringBootTest默认会自动扫描并加载主类所在包下的所有Bean,自然就把这个带run方法的主类也加载进来了,测试时也就跟着执行了那段耗时的逻辑。
解决办法
根据你的测试场景,这里有几种实用方案可选:
1. 直接排除特定的CommandLineRunner Bean
如果测试必须依赖完整Spring上下文,但就是不想触发SampleMain的run方法,可以在@SpringBootTest里用exclude属性直接排除这个类:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(exclude = SampleMain.class) class SampleTest { @Test void test() { assertTrue(true); } }
这样Spring上下文就不会加载SampleMain这个Bean,自然也就不会执行它的run方法了。
2. 用Profile控制主类的加载
给主类加上@Profile("!test"),表示只有当test环境未激活时才会加载这个Bean:
@SpringBootApplication @Profile("!test") public class SampleMain implements CommandLineRunner { private static final Logger logger = LogManager.getLogger(SampleMain.class); public static void main(String[] args) { System.out.println("test"); } @Override public void run(String... args) throws Exception { logger.info("running main.."); } }
然后在测试类里激活test环境:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest @ActiveProfiles("test") class SampleTest { @Test void test() { assertTrue(true); } }
这种方式比较灵活,适合需要在不同环境下控制Bean加载的场景。
3. 用切片测试代替完整上下文加载
如果你的测试不需要完整的Spring应用上下文(比如只是测试某个Service、Repository的逻辑),可以用Spring提供的切片测试注解,比如@ServiceTest、@DataJpaTest等,这些注解只会加载对应层的Bean,不会触发CommandLineRunner的执行:
@RunWith(SpringJUnit4ClassRunner.class) @ServiceTest(classes = YourTargetService.class) class SampleTest { @Autowired private YourTargetService yourService; @Test void test() { // 测试业务逻辑 assertTrue(true); } }
如果是纯单元测试(不需要Spring依赖),甚至可以直接去掉所有Spring注解,只用@Test:
class SampleTest { @Test void test() { // 直接实例化类进行测试 assertTrue(true); } }
这种方式不仅能避免触发主类逻辑,还能大幅提升测试运行速度。
4. 替换CommandLineRunner的实现
在测试类里定义一个空的CommandLineRunner Bean,覆盖原有的实现:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest class SampleTest { @TestConfiguration static class TestConfig { @Bean public CommandLineRunner emptyCommandLineRunner() { return args -> {}; // 空实现,不会执行任何逻辑 } } @Test void test() { assertTrue(true); } }
Spring会优先加载测试类里的TestConfig中的Bean,原有的SampleMain的CommandLineRunner就会被覆盖,不会执行。
总结
如果测试必须依赖完整Spring上下文,优先用排除特定Bean或者Profile控制的方案;如果不需要完整上下文,强烈推荐用切片测试或者纯单元测试,既解决问题又能提升测试效率。
内容的提问来源于stack exchange,提问作者BoomRamada

