在Jackson/SpringBoot中测试自定义JsonDeserializer [英] Testing custom JsonDeserializer in Jackson / SpringBoot

查看:461
本文介绍了在Jackson/SpringBoot中测试自定义JsonDeserializer的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图将单元测试写入自定义反序列化器,该自解序列器使用带有@Autowired参数的构造函数实例化,而我的实体标有@JsonDeserialize.在我的集成测试中,它可以正常工作,其中MockMvc带来了spring服务器端.

I am trying to write a unit test to a custom deserializer that is instantiated using a constructor with an @Autowired parameter and my entity marked with @JsonDeserialize. It works fine in my integration tests where a MockMvc brings up spring serverside.

但是在调用objectMapper.readValue(...)的测试中,使用默认构造函数(不带参数)实例化了反序列化器的新实例.即使

However with tests where objectMapper.readValue(...) is being called, a new instance of deserializer using default constructor with no parameters is instantiated. Even though

@Bean
public MyDeserializer deserializer(ExternalObject externalObject) 

实例化有线版本的反序列化器,实际调用仍传递给空的构造函数,并且上下文未填充.

instantiates wired version of deserializer, real call is still passed to empty constructor and context is not being filled up.

我尝试手动实例化一个反序列化器实例并将其注册到ObjectMapper中,但是仅当我从我的实体类中删除@JsonDeserialize时,该方法才起作用(即使我在@Configuration类中执行相同的操作,也会破坏我的集成测试.) -与此相关: https://github.com/FasterXML/jackson-databind/Issues/1300

I tried manually instantiating of a deserializer instance and registering it in ObjectMapper, but it only works if I remove @JsonDeserialize from my entity class (and it breaks my integration tests even if I do the same in my @Configuration class.) - looks related to this: https://github.com/FasterXML/jackson-databind/issues/1300

我仍然可以直接调用deserializer.deserialize(...)来测试反序列化器的行为,但是这种方法在非Deserializer单元测试中的测试中对我不起作用...

I can still test the deserializer behavior calling deserializer.deserialize(...) directly, but this approach doesn't work for me in tests that are not Deserializer's unit tests...

UPD:下面的工作代码

UPD: working code below

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.github.tomakehurst.wiremock.common.Json;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import java.io.IOException;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

@JsonTest
@RunWith(SpringRunner.class)
public class JacksonInjectExample {
    private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";

    public static class ExternalObject {
        @Override
        public String toString() {
            return "MyExternalObject";
        }
    }

    @JsonDeserialize(using = MyDeserializer.class)
    public static class MyEntity {
        public String field1;
        public String field2;
        public String name;

        public MyEntity(ExternalObject eo) {
            name = eo.toString();
        }

        @Override
        public String toString() {
            return name;
        }
    }

    @Component
    public static class MyDeserializer extends JsonDeserializer<MyEntity> {

        @Autowired
        private ExternalObject external;

        public MyDeserializer() {
            SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        }

        public MyDeserializer(@JacksonInject final ExternalObject external) {
            this.external = external;
        }

        @Override
        public MyEntity deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
            JsonProcessingException {
            return new MyEntity(external);
        }
    }

    @Configuration
    public static class TestConfiguration {
        @Bean
        public ExternalObject externalObject() {
            return new ExternalObject();
        }

        @Bean
        public MyDeserializer deserializer(ExternalObject externalObject) {
            return new MyDeserializer(externalObject);
        }
    }

    @Test
    public void main() throws IOException {
        HandlerInstantiator hi = mock(HandlerInstantiator.class);
        MyDeserializer deserializer = new MyDeserializer();
        deserializer.external = new ExternalObject();
        doReturn(deserializer).when(hi).deserializerInstance(any(), any(), eq(MyDeserializer.class));
        final ObjectMapper mapper = Json.getObjectMapper();
        mapper.setHandlerInstantiator(hi);

        final MyEntity entity = mapper.readValue(JSON, MyEntity.class);
        Assert.assertEquals("MyExternalObject", entity.name);
    }
}

推荐答案

一个非常有趣的问题,使我想知道自动装配成杰克逊解串器在Spring应用程序中如何工作.使用的杰克逊工具似乎是

Very interesting question, it made me wonder how autowiring into jackson deserializers actually works in a spring application. The jackson facility that is used seems to be the HandlerInstantiator interface, which is configured by spring to the SpringHandlerInstantiator implementation, which just looks up the class in the application context.

因此,从理论上讲,您可以在单元测试中使用自己的(模拟的)HandlerInstantiator设置ObjectMapper,并从deserializerInstance()返回准备好的实例.对于其他方法,或者当class参数不匹配时,返回null似乎很好,这将导致jackson自行创建实例.

So in theory you could setup an ObjectMapper in your unit test with your own (mocked) HandlerInstantiator, returning a prepared instance from deserializerInstance(). It seems to be fine to return null for other methods or when the class parameter does not match, this will cause jackson to create the instance on its own.

但是,我认为这不是对单元化反序列化逻辑进行单元测试的好方法,因为ObjectMapper设置必然与实际应用程序执行过程中使用的设置不同.按照Anton答案中的建议使用 JsonTest批注将是一种更好的方法,因为您将获得相同的json配置将在运行时使用.

However, I do not think this is a good way to unit test deserialization logic, as the ObjectMapper setup is necessarily different from what is used during actual application execution. Using the JsonTest annotation as suggested in Anton's answer would be a much better approach, as you are getting the same json configuration that would be used during runtime.

这篇关于在Jackson/SpringBoot中测试自定义JsonDeserializer的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆