Spring Boot JUnit中调用ApplicationContextInitializer及Squiggly测试问题
问题描述
我是Spring Boot新手,最近基于Squiggly做了个带动态实体过滤功能的简单应用,实际跑起来功能完全正常,但就是搞不定JUnit测试这块——网上搜了各种方案试了都没用,我的应用是严格按照Squiggly官方文档建议初始化的。
我写的测试代码大概是这样:
@SpringBootTest @ContextConfiguration(initializers = MyApplicationContextInitializer.class) public class SquigglyFilterTest { @Autowired private RestTemplate restTemplate; @Test public void testDynamicFilter() { // 尝试调用接口验证过滤效果,但一直出问题 } } class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext context) { Squiggly.init(context.getBeanFactory(), new RequestSquigglyContextProvider()); // context.refresh(); // 注释掉的这行,取消注释会出别的错 } }
运行测试时会出现Bean相关的异常,要是取消注释context.refresh(),又会报上下文已经刷新过的错误,实在卡壳了。
解决方案
兄弟,我之前也碰到过这个坑,问题出在Spring Boot测试的上下文初始化时机和Squiggly的初始化逻辑不匹配,给你几个靠谱的解决步骤:
绝对不要手动调用
context.refresh()
Spring Boot的@SpringBootTest会自动帮你完成上下文刷新,手动调用只会触发重复刷新的异常,那行代码就老老实实注释掉吧。换个姿势初始化Squiggly
别用ApplicationContextInitializer了,改用配置类加@PostConstruct的方式——因为ApplicationContextInitializer是在上下文刷新前执行的,这时候很多Bean还没准备好,Squiggly根本没法正确集成。写个配置类就行:
@Configuration public class SquigglyConfig { @Autowired private ConfigurableBeanFactory beanFactory; @PostConstruct public void initSquiggly() { Squiggly.init(beanFactory, new RequestSquigglyContextProvider()); } }简化测试类
去掉测试类上的@ContextConfiguration(initializers = ...),直接用@SpringBootTest就行,Spring Boot会自动扫描加载上面的配置类。另外测试时用TestRestTemplate比普通RestTemplate更顺手,它会自动适配测试环境:@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SquigglyFilterTest { @Autowired private TestRestTemplate testRestTemplate; @Test public void testDynamicFilter() { // 带上Squiggly的过滤参数发起请求,比如只返回id和name ResponseEntity<UserDto> response = testRestTemplate.getForEntity("/users/1?fields=id,name", UserDto.class); UserDto user = response.getBody(); // 验证过滤效果:只有指定字段有值,其他字段为null assertNotNull(user.getId()); assertNotNull(user.getName()); assertNull(user.getEmail()); } }自定义过滤规则的测试技巧
如果你的应用用了自定义过滤规则,比如预定义的filter名称,可以在测试的前置方法里提前注册:@BeforeEach public void setupFilterRules() { Squiggly.register("user-basic", UserDto.class, "id,name"); }然后请求时用
?filter=user-basic就能调用这个预定义规则了。
为啥原来的方式不行?
简单说,ApplicationContextInitializer的执行时机太早了,这时候Spring还没完成Bean的扫描和初始化,Squiggly初始化时没法绑定到Spring的请求处理流程里,自然就起不了作用。而@PostConstruct是在配置类Bean初始化后执行的,这时候Bean工厂已经完全就绪,Squiggly就能正确集成进去了。
内容的提问来源于stack exchange,提问作者Jean-Marc Astesana

