关于 Junit 5 测试用例类中使用构造器注入不生效的问题排查
一、问题描述
在使用 Junit 5 写 Spring Boot 项目测试用例的时候,发现一个问题:使用构造器注入 Service 类的时候,执行测试用例时,会显示如下异常:
1 | org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [cn.z2huo.demo.mapper.user.UserMapper arg0] in constructor [public cn.z2huo.demo.mapper.user.UserMapperTest(cn.z2huo.demo.mapper.user.UserMapper)]. |
经过测试 UserMapper
是已经注册到容器中的,并且在 main 中使用构造器注入是可以获取到 UserMapper
的,但是在 test 中单纯使用构造器注入却获取不到,而使用带有 @Autowired
的属性注入或带有 @Autowired
的构造器注入是可以获取到的。猜测是在使用 spring 中使用 junit 5 的问题。
另外,几种装配 Bean 的方式如下:
1 |
|
二、排查问题
从异常 ParameterResolutionException
入手,定位问题,具体报错位置为 org.junit.jupiter.engine.execution.ParameterResolutionUtils#resolveParameter
方法,该方法中有如下判断:
1 | List<ParameterResolver> matchingResolvers = extensionRegistry.stream(ParameterResolver.class) |
这段代码会检查 ParameterResolver
是否支持为提供的 ExtensionContext
中的 ParameterContext
所指定的参数解析实参。
Spring + Junit 5 需要使用 @SpringBootTest
注解,或者添加 SpringExtension
扩展,SpringExtension
扩展实现了 ParameterResolver
接口,SpringExtension 中的 supportsParameter
方法如下:
1 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { |
SpringExtension#supportsParameter
方法确定提供的 ParameterContext
中的参数值是否应从测试的 ApplicationContext
中自动装配。
如果满足以下任一条件,则认为参数是可以自动装配的:
- 声明该参数的可执行文件是构造函数,并且
TestConstructorUtils.isAutowirableConstructor(Constructor, Class, PropertyProvider)
返回true
。注意,isAutowirableConstructor()
将使用一个回退的PropertyProvider
调用,该提供者将其查找委托给ExtensionContext.getConfigurationParameter(String)
。 - 参数类型为
ApplicationContext
或其子类型。 - 参数类型为
ApplicationEvents
或其子类型。 ParameterResolutionDelegate.isAutowirable
返回true
。
警告:如果测试类的构造函数被 @Autowired
注解或自动装配(参见 @TestConstructor
),Spring 将负责解析构造函数中的所有参数。因此,其他注册的 ParameterResolver
将无法解析这些参数。
可以看到,报错的直接原因就是 SpringExtension#supportsParameter
方法的返回结果为 false,并且因为是构造函数自动装配 Bean 的问题,该判断中这块功能的判断就是 TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider)
方法。
isAutowirableConstructor
方法用来确定给定测试类的提供的可执行文件是否是可自动装配的构造函数。如果提供的可执行文件是构造函数,则此方法会委托给 isAutowirableConstructor(Constructor, Class, PropertyProvider)
进行判断;否则返回 false
。
isAutowirableConstructor
重载方法内容如下:
1 | public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass, |
该方法会判断构造方法是否支持自动装配,方法中有两个分支,一个是判断 @TestConstructor
注解,另一个是去寻找 spring properties 中的 spring.test.constructor.autowire.mode
配置。
其中涉及到的枚举如下:
1 | enum AutowireMode { |
AutowireMode
枚举类中的两个成员含义如下:
ALL
,所有测试构造函数的参数都将被自动装配,就好像该构造函数本身被注解了@Autowired
、@jakarta.inject.Inject
或@javax.inject.Inject
一样。ANNOTATED
,每个单独的测试构造函数参数只有在被@Autowired
、@Qualifier
或@Value
注解时,或者当构造函数本身被@Autowired
、@jakarta.inject.Inject
或@javax.inject.Inject
注解时,才会被自动装配。
1、解决方案一
添加 TestConstructor
注解:
1 |
|
2、解决方案二
添加 spring 属性,在 resources 目录下新建文件 spring.properties
并在其中添加如下配置:
1 | spring.test.constructor.autowire.mode=ALL |
三、其他
1、Spring 官方文档对 TestConstructor 注解的描述
@TestConstructor
是一个可以应用于测试类的注解,用于配置测试类构造函数的参数如何从测试的 ApplicationContext
中的组件进行自动装配。
如果测试类上没有 @TestConstructor
或者元注解形式的 @TestConstructor
,则会使用默认的测试构造函数自动装配模式。然而,需要注意的是,构造函数上的本地声明 @Autowired
、@jakarta.inject.Inject
或 @javax.inject.Inject
优先于 @TestConstructor
和默认模式。
@TestConstructor
仅在与 SpringExtension
结合使用时支持,适用于 JUnit Jupiter。请注意,SpringExtension
经常会自动为你注册——例如,当你使用 @SpringJUnitConfig
和 @SpringJUnitWebConfig
注解或 Spring Boot Test 中的各种测试相关注解时。
1.1 更改默认测试构造函数自动装配模式
可以通过设置 JVM 系统属性 spring.test.constructor.autowire.mode
为 all
来更改默认的测试构造函数自动装配模式。添加如下参数:-Dspring. test. constructor. autowire. mode=all
或者,也可以通过 SpringProperties
机制来设置默认模式。
此外,默认模式还可以配置为 JUnit 平台的配置参数。
如果未设置 spring.test.constructor.autowire.mode
属性,则测试类的构造函数将不会被自动装配。
相关链接
Spring JUnit Jupiter Testing Annotations :: Spring Framework
OB links
OB tags
#Junit #Spring #Junit5