Spring Boot中如何对自定义StdDeserializer编写单元测试
问题场景
待反序列化的JSON对象格式如下:
{ "myTypes":["type-1","type-2"...] }
对应的自定义Jackson反序列化类实现如下:
class MyDeserializer: StdDeserializer<MyType>( MyType::class.java) { override fun deserialize(jsonParser: JsonParser?, dctx: DeserializationContext?): MyType? { val node: JsonNode = jsonParser?.codec?.readTree(jsonParser) ?: return null return try { MyType.valueOf(node.textValue().uppercase().replace('-','_')) }catch (e : IllegalArgumentException){ null } } }
当前已完成大部分测试代码编写,但不清楚调用deserialize方法时需要传入什么参数,现有测试框架代码如下:
class MyTypeDeserializerTest { @BeforeEach fun init() { MockKAnnotations.init(this) } @ParameterizedTest(name = "given string \"{0}\" is valid: \"{1}\"") @MethodSource("tests") fun `check different parameters`(string: String, myType: MyType) { val myTypeDeserializer = MyDeserializer() val deserialisedType= myTypeDeserializer.deserialize(<what do I put here?>) Assertions.assertEquals(myType, deserialisedType) } private companion object { @JvmStatic fun tests() = Stream.of( Arguments.of(String.format(<json>), expectedType), ) } }
解决方案
不推荐手动MockJsonParser和DeserializationContext参数,直接使用Jackson原生ObjectMapper生成解析实例即可,这种方式更贴近实际运行逻辑,不会出现Mock行为和真实逻辑不一致的问题。
完整可运行的测试代码参考如下:
import com.fasterxml.jackson.databind.ObjectMapper import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import io.mockk.MockKAnnotations import java.util.stream.Stream class MyTypeDeserializerTest { private val objectMapper = ObjectMapper() private lateinit var myTypeDeserializer: MyDeserializer @BeforeEach fun init() { MockKAnnotations.init(this) myTypeDeserializer = MyDeserializer() } @ParameterizedTest(name = "given input \"{0}\", expected result: {1}") @MethodSource("tests") fun `check different parameters`(inputJson: String, expected: MyType?) { val jsonParser = objectMapper.factory.createParser(inputJson) // 直接通过parser调用readValue传入反序列化器即可,不需要手动传context val result = jsonParser.readValueAs(myTypeDeserializer) Assertions.assertEquals(expected, result) } private companion object { @JvmStatic fun tests() = Stream.of( // 合法值测试:中划线转下划线、大写后匹配枚举 Arguments.of("\"type-1\"", MyType.TYPE_1), Arguments.of("\"type-2\"", MyType.TYPE_2), // 非法值测试,捕获异常后返回null Arguments.of("\"invalid-type\"", null), Arguments.of("\"\"", null) ) } }
如果要直接调用双参数的deserialize方法,按如下规则传参即可:
- 第一个参数
jsonParser:通过objectMapper.factory.createParser(输入JSON字符串)创建,注意创建后要先调用jsonParser.nextToken()让解析器定位到待解析的值节点,否则读取会报错 - 第二个参数
dctx:可以直接传null,你的反序列化逻辑里没有用到上下文相关能力,传空不影响运行;如果需要完整上下文,传入objectMapper.deserializationContext即可
注意事项:
- 你写的自定义反序列化器是处理单个
MyType枚举值的,参数化测试的入参应该是单个枚举值对应的JSON字符串(比如"type-1",要带JSON字符串要求的双引号),不要传外层包裹myTypes数组的完整JSON,数组的遍历解析是Jackson内置逻辑处理的,不属于单个枚举反序列化器的测试范围 - 如果后续要测试完整数组的反序列化,直接定义包含
myTypes: List<MyType>字段的包装类,给字段加上@JsonDeserialize(contentAs = MyDeserializer::class)注解,直接用ObjectMapper读整个JSON字符串即可,不需要单独调用反序列化器
内容的提问来源于stack exchange,提问作者user12115080
相关产品推荐
相关产品推荐

