JUnit 5 自定义注解:方法级 JSON 参数注入

JUnit 5 自定义注解:方法级 JSON 参数注入

为了实现 在测试方法上使用注解,并通过注解属性指定参数名称和 JSON 字符串(转换为 Java 对象),以下是基于 JUnit 5 正确扩展接口的解决方案:


一、实现步骤
1. 定义自定义注解

创建一个注解 @JsonTest,用于在测试方法上指定参数名和 JSON 字符串。

java 复制代码
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 转换为对象并存储到上下文。

java 复制代码
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

从上下文中获取存储的对象并注入到测试参数。

java 复制代码
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. 注册扩展

在测试类中注册自定义扩展。

java 复制代码
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 类匹配。

java 复制代码
public class Person {
    private String name;
    private int age;
    // Getter & Setter
}

public class Address {
    private String city;
    private String zipCode;
    // Getter & Setter
}

三、关键点说明
  1. 参数名匹配

    • 需要启用编译时保留参数名(Java 8+ 使用 -parameters 编译选项)。
    • 确保 @JsonTest(parameterName="xxx") 与测试方法参数名一致。
  2. JSON 转换

    • 使用 JacksonGson 反序列化 JSON。

    • 依赖示例(Maven):

      xml 复制代码
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.13.3</version>
      </dependency>
  3. 上下文存储

    • 使用 ExtensionContext.Store 临时存储参数值,避免线程安全问题。

四、扩展功能
1. 支持多参数

若需注入多个参数,可将注解改为数组形式:

java 复制代码
@JsonTests({
    @JsonTest(parameterName = "person", jsonValue = "..."),
    @JsonTest(parameterName = "address", jsonValue = "...")
})
@Test
void testMultipleParams(Person person, Address address) { ... }

BeforeEachCallback 中遍历 @JsonTests 注解,解析每个 @JsonTest 并存储参数值。

java 复制代码
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: 前缀:

java 复制代码
String jsonValue = annotation.jsonValue();
if (jsonValue.startsWith("file:")) {
    String path = jsonValue.substring("file:".length());
    jsonValue = Files.readString(Paths.get(path));
}

五、总结

通过结合 BeforeEachCallbackParameterResolver,实现了以下功能:

  • 方法级注解:在测试方法上直接指定参数名和 JSON 数据。
  • 自动类型转换:将 JSON 字符串转换为复杂 Java 对象。
  • 灵活扩展:支持文件加载、多参数等场景。

优势

  • 代码简洁:测试方法参数与 JSON 数据解耦。
  • 高可读性:注解明确指定数据来源和参数映射。
  • 兼容 JUnit 5:严格遵循扩展模型,无侵入式设计。

适用场景

  • API 测试:直接注入请求参数对象。
  • 数据驱动测试:从外部 JSON 文件加载测试用例。
  • 复杂对象验证:嵌套 JSON 结构映射到领域模型。
相关推荐
咖啡啡不加糖11 分钟前
RabbitMQ 消息队列:从入门到Spring Boot实战
java·spring boot·rabbitmq
玩代码18 分钟前
Java线程池原理概述
java·开发语言·线程池
NE_STOP21 分钟前
SpringBoot--如何给项目添加配置属性及读取属性
java
水果里面有苹果24 分钟前
20-C#构造函数--虚方法
java·前端·c#
%d%d227 分钟前
python 在运行时没有加载修改后的版本
java·服务器·python
金銀銅鐵33 分钟前
[Kotlin] 单例对象是如何实现的?
java·kotlin
泰勒疯狂展开34 分钟前
Java研学-MongoDB(三)
java·开发语言·mongodb
zzywxc78741 分钟前
AI技术通过提示词工程(Prompt Engineering)正在深度重塑职场生态和行业格局,这种变革不仅体现在效率提升,更在重构人机协作模式。
java·大数据·开发语言·人工智能·spring·重构·prompt
张先shen1 小时前
Elasticsearch RESTful API入门:索引的增删改查完全指南
java·大数据·elasticsearch·搜索引擎·架构·全文检索·restful
天天摸鱼的java工程师1 小时前
工作八年,如果现在让我重做“教务系统”毕业设计,我会这样答...
java·后端