Loading

SpringBoot测试迷你系列 —— 四 使用@JsonTest测试序列化

使用@JsonTest测试序列化

原文Testing Serialization With Spring Boot @JsonTestTesting Web Controllers With Spring Boot @WebMvcTest

目录

  1. 单元测试
  2. 使用@WebMvcTest进行测试
  3. 使用@DataJpa进行持久层测试
  4. 使用@JsonTest测试序列化
  5. 使用MockWebServer测试Spring WebClient Rest调用
  6. 使用@SpringBootTest进行SpringBoot集成测试

非逐句翻译

前导知识

可能本篇的所有测试都基于这个pojo,先对于这个Pojo中使用到的一些内容进行说明。

public class Receipt {
    private LocalDateTime date;
    private String creditCardNumber;
    private MonetaryAmount amount;
    // ... setter and getter ...
}

MonetaryAmount

JSR354引入的关于货币符号的类型,可以表示一个具体数值或范围,比如50美元或10-20欧元,是一套接口,没有实现。

Moneta

是一个MonetaryAmount标准的实现

JavaMoney 'Moneta' User Guide

引入

<properties>
    <java.version>11</java.version>
    <javax.money.version>1.1</javax.money.version>
    <javamoney.moneta.version>1.4.1</javamoney.moneta.version>
</properties>

<!-- .......... -->

<dependency>
    <groupId>javax.money</groupId>
    <artifactId>money-api</artifactId>
    <version>${javax.money.version}</version>
</dependency>
<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>${javamoney.moneta.version}</version>
    <type>pom</type>
</dependency>

@JsonComponet

用于定义Jackson的Json序列化和反序列化器

由于MonetaryAmount过于复杂,里面有很多用户不关心的属性,所以我们需要创建一个序列化和反序列化器用于将MonetaryAmount转换给用户并且从用户传递的简单字符串得到MonetaryAmount对象。

如下是一个简单的Controller

@RestController
@RequestMapping("/receipt")
public class ReceiptController {

   @GetMapping
   public Receipt receipt(){
      return new Receipt(LocalDateTime.now(), "123-123-123", Money.of(200, "USD"));
   }

   @PostMapping
   public void receipt(@RequestBody Receipt receipt) {
      System.out.println(receipt);
   }

}

没使用序列化器

使用序列化器

序列化器代码,应该不难理解吧

@JsonComponent
public class MoneySerialization {
    private static MonetaryAmountFormat monetaryAmountFormat;
    static {
        monetaryAmountFormat = MonetaryFormats.getAmountFormat(
                LocaleContextHolder.getLocale()
        );
    }

    static class MonetaryAmountSerializer extends StdSerializer<MonetaryAmount> {

        protected MonetaryAmountSerializer() {
            super(MonetaryAmount.class);
        }

        @Override
        public void serialize(MonetaryAmount monetaryAmount, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeString(monetaryAmountFormat.format(monetaryAmount));
        }
    }

    static class MonetaryAmountDeserializer extends StdDeserializer<MonetaryAmount> {

        protected MonetaryAmountDeserializer() {
            super(MonetaryAmount.class);
        }

        @Override
        public MonetaryAmount deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
            return monetaryAmountFormat.parse(jsonParser.readValueAs(String.class));
        }
    }
}

LocalDateTime不需要序列化器吗?

Jackson已经定义过了。

@WebMvcTest够了吗?

一般时候够,当我们有序列化器的时候,我们一般情况下都想对序列化器和反序列化器做单独的测试,而不是在Web层面的测试。

使用@JsonTest进行集成测试

@JsonTest自动配置Jackson相关的内容,任何@JsonComponent注解标注的类以及任何Jackson模块。因为Spring只加载它所需要的,所以这要比Controller测试更加轻量级。

如果你是用GsonJsonb,SpringBoot也会自动配置它们的bean。

@JsonTest
public class ReceiptControllerTest {

    @Autowired
    private JacksonTester<Receipt> jacksonTester;

    // ...

}

Spring也自动配置了一个基于AssertJ的JacksonTester,并且和JsonAssert和JsonPath库一起工作。

测试序列化

@Test
void serializeInCorrectFormat() throws IOException {
    Receipt receipt = new Receipt(
            LocalDateTime.now(),
            "123-123-123",
            Money.of(200, "USD"));

    JsonContent<Receipt> json = jacksonTester.write(receipt);
    assertThat(json).extractingJsonPathStringValue("$.amount").isEqualTo("USD200.00");
    assertThat(json).extractingJsonPathStringValue("$.creditCardNumber").isEqualTo("123-123-123");
}

上面我们在AssertJ的断言中使用了JsonPath语法,我们可以通过这些断言来检查我们感兴趣的字段。

我们也可以通过json文件来进行断言,而不用对每一个字段编写断言。

@Test
void testJsonFormatWithFile() throws IOException {
    Receipt receipt = new Receipt(
            LocalDateTime.of(2021, 5, 9, 16, 0),
            "123-123-123",
            Money.of(200, "USD"));

    JsonContent<Receipt> json = jacksonTester.write(receipt);

    assertThat(json).isEqualToJson("/receipt.json");
}

Json文件

{
  "date": "2021-05-09T16:00:00",
  "creditCardNumber": "123-123-123",
  "amount": "USD200.00"
}

isEqualToJson方法当传入的字符串以.json结尾时,会使用ClassPathResource去加载文件,否则就认为传入的参数就是一个json字符串。

测试反序列化

@Test
void deserializeFromCorrectFormat() throws IOException {
    MonetaryAmount exceptedAmount = Money.of(200, "USD");
    Receipt receipt = jacksonTester.parseObject("{\n" +
            "  \"date\": \"2021-05-09T16:00:00\",\n" +
            "  \"creditCardNumber\": \"123-123-123\",\n" +
            "  \"amount\": \"USD200.00\"\n" +
            "}");

    assertThat(receipt.getAmount()).isEqualTo(exceptedAmount);
}

通过文件

@Test
void deserializeFromCorrectFormatByFile() throws IOException{
    MonetaryAmount exceptedAmount = Money.of(200, "USD");
    Receipt receipt = jacksonTester.readObject("/receipt.json");

    assertThat(receipt.getAmount()).isEqualTo(exceptedAmount);
}
posted @ 2021-10-11 14:39  yudoge  阅读(620)  评论(0编辑  收藏  举报