一尘不染

在Jackson / SpringBoot中测试自定义JsonDeserializer

spring-boot

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

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

@Bean
public MyDeserializer deserializer(ExternalObject externalObject)

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

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

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

UPD:下面的工作代码

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);
    }
}

阅读 2242

收藏
2020-05-30

共1个答案

一尘不染

一个非常有趣的问题,使我想知道自动插入杰克逊解串器在弹簧应用程序中如何工作。使用的jackson工具似乎是HandlerInstantiatorinterface,它是由spring配置到SpringHandlerInstantiator实现的,该实现只是在应用程序上下文中查找类。

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

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

2020-05-30