You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 22:16:01