JUnit 5 自定义注解:方法级 JSON 参数注入
JUnit 5 自定义注解:方法级 JSON 参数注入
为了实现 在测试方法上使用注解,并通过注解属性指定参数名称和 JSON 字符串(转换为 Java 对象),以下是基于 JUnit 5 正确扩展接口的解决方案:
一、实现步骤
1. 定义自定义注解
创建一个注解 @JsonTest
,用于在测试方法上指定参数名和 JSON 字符串。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonTest {
String parameterName(); // 方法参数的名称
String jsonValue(); // JSON 字符串
}
2. 实现 BeforeEachCallback
在测试方法执行前解析注解,将 JSON 转换为对象并存储到上下文。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class JsonParameterPreprocessor implements BeforeEachCallback {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Method testMethod = context.getRequiredTestMethod();
if (testMethod.isAnnotationPresent(JsonTest.class)) {
JsonTest annotation = testMethod.getAnnotation(JsonTest.class);
String parameterName = annotation.parameterName();
String jsonValue = annotation.jsonValue();
// 遍历方法参数,匹配参数名
for (Parameter param : testMethod.getParameters()) {
if (param.getName().equals(parameterName)) {
// 将 JSON 转换为对象
Object parameterValue = objectMapper.readValue(jsonValue, param.getType());
// 存储到上下文
context.getStore(ExtensionContext.Namespace.GLOBAL)
.put(parameterName, parameterValue);
break;
}
}
}
}
}
3. 实现 ParameterResolver
从上下文中获取存储的对象并注入到测试参数。
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
public class JsonParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
String paramName = paramCtx.getParameter().getName();
return extCtx.getStore(ExtensionContext.Namespace.GLOBAL).get(paramName) != null;
}
@Override
public Object resolveParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
String paramName = paramCtx.getParameter().getName();
return extCtx.getStore(ExtensionContext.Namespace.GLOBAL).get(paramName);
}
}
4. 注册扩展
在测试类中注册自定义扩展。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith({JsonParameterPreprocessor.class, JsonParameterResolver.class})
public class JsonParameterTest {
@JsonTest(parameterName = "person", jsonValue = "{\"name\":\"Alice\",\"age\":25}")
@Test
void testPerson(Person person) {
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
Assertions.assertEquals("Alice", person.getName());
}
@JsonTest(parameterName = "address", jsonValue = "{\"city\":\"New York\",\"zipCode\":\"10001\"}")
@Test
void testAddress(Address address) {
System.out.println("City: " + address.getCity() + ", Zip Code: " + address.getZipCode());
Assertions.assertEquals("New York", address.getCity());
}
}
二、数据模型定义
确保 JSON 结构与 Java 类匹配。
public class Person {
private String name;
private int age;
// Getter & Setter
}
public class Address {
private String city;
private String zipCode;
// Getter & Setter
}
三、关键点说明
-
参数名匹配
- 需要启用编译时保留参数名(Java 8+ 使用
-parameters
编译选项)。 - 确保
@JsonTest(parameterName="xxx")
与测试方法参数名一致。
- 需要启用编译时保留参数名(Java 8+ 使用
-
JSON 转换
- 使用 Jackson 或 Gson 反序列化 JSON。
- 依赖示例(Maven):
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency>
-
上下文存储
- 使用
ExtensionContext.Store
临时存储参数值,避免线程安全问题。
- 使用
四、扩展功能
1. 支持多参数
若需注入多个参数,可将注解改为数组形式:
@JsonTests({
@JsonTest(parameterName = "person", jsonValue = "..."),
@JsonTest(parameterName = "address", jsonValue = "...")
})
@Test
void testMultipleParams(Person person, Address address) { ... }
在 BeforeEachCallback
中遍历 @JsonTests
注解,解析每个 @JsonTest
并存储参数值。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class JsonParameterPreprocessor implements BeforeEachCallback {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Method testMethod = context.getRequiredTestMethod();
// 检查是否带有 @JsonTests 注解
if (testMethod.isAnnotationPresent(JsonTests.class)) {
JsonTests annotations = testMethod.getAnnotation(JsonTests.class);
// 遍历每个 @JsonTest 注解
for (JsonTest annotation : annotations.value()) {
String parameterName = annotation.parameterName();
String jsonValue = annotation.jsonValue();
// 遍历方法参数,匹配参数名
for (Parameter param : testMethod.getParameters()) {
if (param.getName().equals(parameterName)) {
// 将 JSON 转换为对象
Object parameterValue = objectMapper.readValue(jsonValue, param.getType());
// 存储到上下文
context.getStore(ExtensionContext.Namespace.GLOBAL)
.put(parameterName, parameterValue);
break;
}
}
}
}
}
}
2. 从文件加载 JSON
修改 JsonParameterPreprocessor
,支持 file:
前缀:
String jsonValue = annotation.jsonValue();
if (jsonValue.startsWith("file:")) {
String path = jsonValue.substring("file:".length());
jsonValue = Files.readString(Paths.get(path));
}
五、总结
通过结合 BeforeEachCallback
和 ParameterResolver
,实现了以下功能:
- 方法级注解:在测试方法上直接指定参数名和 JSON 数据。
- 自动类型转换:将 JSON 字符串转换为复杂 Java 对象。
- 灵活扩展:支持文件加载、多参数等场景。
优势:
- 代码简洁:测试方法参数与 JSON 数据解耦。
- 高可读性:注解明确指定数据来源和参数映射。
- 兼容 JUnit 5:严格遵循扩展模型,无侵入式设计。
适用场景:
- API 测试:直接注入请求参数对象。
- 数据驱动测试:从外部 JSON 文件加载测试用例。
- 复杂对象验证:嵌套 JSON 结构映射到领域模型。