当前位置: 首页 > 知识库问答 >
问题:

在Jackson/SpringBoot中测试自定义的JsonDeserizer

罗渝
2023-03-14

我正在尝试编写一个单元测试到一个自定义反序列化器,该反序列化器是使用一个带有@了的构造函数来实例化的,并且我的实体标有@JsonDesri的。它在我的集成测试中工作得很好,其中MockMvc会带来Spring serverside。

然而,在调用objectMapper.read值(…)的测试中,使用不带参数的默认构造函数的反序列化器的新实例被实例化。即使

@Bean
public MyDeserializer deserializer(ExternalObject externalObject) 

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

我尝试手动实例化反序列化程序实例并将其注册到 ObjectMapper 中,但它仅在我从实体类中删除@JsonDeserialize时才有效(即使我在 @Configuration 类中执行相同操作,它也会破坏我的集成测试。 - 与此相关的外观:https://github.com/FasterXML/jackson-databind/issues/1300

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

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

共有3个答案

臧亦
2023-03-14

单元测试不应依赖或调用其他主要类或框架。如果也有集成测试或验收测试覆盖应用程序的功能,并且您描述了一组特定的依赖项,则尤其如此。因此,最好编写单元测试,以便它有一个类作为主题,即直接调用deserializer.deserialize(…)。

在这种情况下,单元测试应该包括使用模拟或存根的 ExternalObject 实例化 MyDeserializer,然后测试其 deserialize() 方法是否针对 JsonParser 和 DeserializationContext 参数的不同状态正确返回 MyEntity。Mockito非常适合设置模拟依赖项!

通过在单元测试中使用ObjectMapper,来自Jackson框架的大量代码也在每次运行中被调用——所以测试不是验证MyDeserializer的契约,而是验证MyDeserializer和Jackson的特定版本的组合的行为。如果测试失败,将不会立即清楚所有相关组件中的哪一个有问题。因为同时设置两个框架的环境更加困难,随着时间的推移,测试将变得脆弱,并且由于测试类中的设置问题而更容易失败。

Jackson框架负责使用@JacksonInject编写ObjectMapper.read值和构造函数的单元测试。对于“不是反序列化器单元测试的其他单元测试”——最好模拟/存根该测试的MyDesriezer(或其他依赖项)。这样,另一个类的逻辑就与MyDesriezer中的逻辑隔离开来——另一个类的契约可以被验证,而不会被测试单元之外的代码行为所限定。

谈禄
2023-03-14

非常有趣的问题,它让我想知道在spring应用程序中如何自动连接到jackson反序列化器。所使用的jackson工具似乎是< code>HandlerInstantiator接口,它由spring配置为< code > SpringHandlerInstantiator 实现,后者只是在应用程序上下文中查找类。

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

但是,我不认为这是单元测试反序列化逻辑的好方法,因为< code>ObjectMapper设置必然不同于实际应用程序执行期间使用的设置。使用Anton答案中建议的< code>JsonTest注释将是一个更好的方法,因为您将获得与运行时相同的json配置。

章彬郁
2023-03-14

我不知道如何使用Jackson注入来设置这一点,但您可以使用SpringJson测试来测试它。我认为这种方法更接近实际情况,也更简单。Spring将只加载与序列化/反序列化bean相关的bean,因此您必须只提供自定义bean或mock来代替它们。

@JsonTest
public class JacksonInjectExample {

  private static final String JSON = "{\"field1\":\"value1\", \"field2\":123}";

  @Autowired
  private JacksonTester<MyEntity> jacksonTester;

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

  @Test
  public void test() throws IOException {
      MyEntity result = jacksonTester.parseObject(JSON);
      assertThat(result.getName()).isEqualTo("MyExternalObject");
  }

如果您想使用模拟,请使用以下代码片段:

  @MockBean
  private ExternalObject externalObject;

  @Test
  public void test() throws IOException {
      when(externalObject.toString()).thenReturn("Any string");
      MyEntity result = jacksonTester.parseObject(JSON);
      assertThat(result.getName()).isEqualTo("Any string");
  }
 类似资料:
  • 全部的 我在我的主要Spring Boot应用程序类中定制了,如下所示。 其中,是的扩展。我的POJO是用Jackson的注释的 我使用和注释通过HTTP公开了POJO。这很好,当我运行应用程序并访问endpoint时,会调用自定义过滤器。 现在我编写了一个集成测试,如下所示。 当我运行测试时,obj为null,我看到一个错误,没有配置FilterProvider 我做错了什么,如何纠正? 我的文

  • 我有一个自定义任务定义来运行每个测试具有特殊设置的特定测试文件。我的任务定义如下: 现在,此设置中的一些测试是不可靠的,我尝试再次运行它们,如下所示: 我编写了一个测试类,第一次总是失败,第二次总是成功: 不幸的是,测试只执行一次,整个测试套件失败。我可以使用中所述的自定义规则成功运行测试https://stackoverflow.com/a/55178053/6059889 有没有办法将测试重试

  • 我编写了以下来让Jackson将一个数组的整数序列化为JSON: 此处使用该类: 我想测试序列化程序的行为,并得出以下结论: 但是,不向写入任何内容。我做错了什么?

  • 我正在使用Spring Boot创建一个访问数据库的简单web应用程序。通过在中设置属性,我利用了DataSource的自动配置功能。这一切都很出色,而且非常快--伟大的工作伙计们@Spring! 我公司的政策是不应该有明文密码。因此,我需要对进行加密。经过一番深入研究,我决定创建一个实现,该实现创建一个jasypt,如下所示: 然后,我用文件将其打包到它自己的jar中,如下所示: 当在maven

  • 本机Spring Boot自动配置(例如one)也可以检测主类中声明的bean(例如注释的方法)。 如何对主类中声明的bean进行正确的bean检测? 编辑

  • 问题内容: 我正在使用带有Spring Web服务且没有Spring Boot的Spring Framework版本4.1.6。要学习该框架,我正在编写REST API并进行测试以确保从命中端点收到的JSON响应正确。具体来说,我想调整的‘用下划线小写’命名策略使用。 我正在使用Spring博客上详细介绍的方法创建一个新方法,并将其添加到转换器列表中。如下所示: 然后,运行以下测试(使用JUnit