1. 程式人生 > 其它 >SpringBoot測試迷你係列 —— 四 使用@JsonTest測試序列化

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