Spring Boot中的Json序列化单元测试



引言


每个Web服务的主要任务之一是将模型返回到客户端,在这种情况下, Spring Boot提供了一个方便的抽象级别,允许开发人员保持使用模型的级别,并将模型序列化过程留在程序源代码之外。 但是,如果序列化本身成为应用程序业务逻辑的一部分并因此需要测试用例覆盖,该怎么办?

本文将研究其中一种情况,当我们可能需要在序列化期间考虑应用程序的业务逻辑的特殊性(舍入金额的情况)时,我们将在Spring Boot中遇到序列化机制的示例,并描述一种可能的测试方法。

问题陈述


让我们的Web服务负责提供有关客户费用的信息,并且我们需要提供可配置精度的数据。 逻辑解决方案是将所有模型转换到服务的外围,同时保留应用舍入逻辑的可见性。

考虑可能的解决方案


想象一下我们应用程序的控制器,它将返回所需的模型。

@RestController public class AccountController { //        , //       . @Autowired private AccountService accountService; @RequestMapping(value = "/account/{clientId}", method = RequestMethod.GET, produces = "application/json") public Account getAccount(@PathVariable long clientId) throws Exception { Account result = accountService.getAccount(clientId); //  ,    - //      ,    json, //    . return result; } } 

现在让我们看看我们的模型。

 public class Account { private Long clientId; //    Spring Boot   FasterXML/jackson, //    API  ,   . // ,       //     MoneySerializer @JsonSerialize(using = MoneySerializer.class) private BigDecimal value; //    getter'  setter'    } 

您可能已经不得不处理其他用于自定义的注释。 此批注的一个功能是能够确定负责序列化批注模型字段的服务。

在研究序列化器的全部含义之前,我们将使任务复杂化:通过一些抽象动态参数解析形式的内部服务来配置舍入参数。

这种复杂性是我们要考虑的关键点。 从模型的实现中可以看出,我们框架的API在参数中采用了序列化类,这意味着序列化程序的生命周期受序列化程序框架的控制。 这就引出了一个问题,如果我们想将应用程序上下文中的依赖项注入序列化器,该怎么办? 为此,请考虑上述串行器的实现。

 //     , //  Jackson   Spring, // API    //    Spring DI @JsonComponent public class MoneySerializer extends JsonSerializer<BigDecimal> { //  ,   , //    Spring Boot    Bean'. private RoundingHolder roundingHolder; @Autowired public MoneySerializer(RoundingHolder roundingHolder) { this.roundingHolder = roundingHolder; } //       , // ,   ,      - //      . @Override public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeNumber(value.setScale(roundingHolder.getPrecision(), roundingHolder.getRoundingMode())); } } 

我们的服务已经准备就绪,但是作为负责任的开发人员,我们希望确保组装的厨房能正常工作。

让我们继续进行测试。


让我们看看测试框架API为我们提供了什么。

 //     ,   //    Spring,       . //     JsonTest,      //      , //    JSON-. @JsonTest @ContextConfiguration(classes = {AccountSerializationTest.Config.class}) @RunWith(SpringRunner.class) public class AccountSerializationTest { //  ,      //     ObjectMapper,    . //       . //    , //      . @Autowired private ObjectMapper objectMapper; @Test public void testAccountMoneyRounding() throws Exception { Account account = new Account(); account.setClientId(1L); account.setValue(BigDecimal.valueOf(1.123456789)); String expectedResult = "{\"clientId\":1,\"value\":\1.123\}"; // ,          JSON, //     -. assertEquals(expectedResult, objectMapper.writeValueAsString(account)); } //   MoneySerializer   API  //    ,       //    Jackson.   ,   , //   Spring , ,  //      . @TestConfiguration public static class Config { @Bean public static RoundingHolder roundingHolder() { RoundingHolder roundingHolder = Mockito.mock(RoundingHolder.class); //   ,         Mockito.when(roundingHolder.getMathContext()).thenReturn(new MathContext(3, RoundingMode.HALF_EVEN)); return roundingHolder; } } } 

让我们更详细地讨论这一刻。 Jackson使用ObjectMapper类对模型进行序列化和反序列化。 这正是负责模型转换的上下文对象,因此,要确保将如何显示模型,您需要检查ObjectMapper如何从上下文处理它。

如果要创建自己的自定义ObjectMapper,可以遇到以下典型示例: ObjectMapper mapper = new ObjectMapper 。 但是,请查看默认情况下Spring Boot如何创建此类的实例。 为此,我们转到原始的JacksonAutoConfiguration自动配置代码 ,该代码负责创建对象:

 @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { return builder.createXmlMapper(false).build(); } 

而且,如果我们进一步研究build() ,我们会发现对于序列化(使用默认映射器(例如将服务注入自定义序列化器))可以习惯使用的序列化,仅创建Bean映射器还不够,您应该转向提供的构建器。 顺便说一句,在Spring Boot 文档本身中,对此进行了明确说明。

题外话,我想添加对JacksonTester的引用。 作为Mockito上下文中BDD序列化测试的Shell的代表。

总结一下


  • Spring Boot通过JsonSerializer注释提供了定制模型序列化的功能
  • 要测试序列化,请在与应用程序相同的配置中使用映射器
  • 从Spring Boot自动配置覆盖Bean时,请注意该Bean如何创建Spring Boot本身,以免错过默认Bean拥有的机会
  • 您可以使用JsonTest批注指定测试序列化所需的受限上下文。

结论


感谢您的关注! 此示例与Spring Boot 2.1.x的当前版本以及1.4.x之前的早期版本有关。 同样,该技术适用于反序列化模型的情况。 查看核心框架的内幕,以更好地了解应用程序的运行机制,并采取负责任的方法进行测试。

Source: https://habr.com/ru/post/zh-CN452188/


All Articles