SpringBoot測試迷你係列 —— 四 使用@JsonTest測試序列化
使用@JsonTest測試序列化
原文Testing Serialization With Spring Boot @JsonTestTesting Web Controllers With Spring Boot @WebMvcTest
目錄
- 單元測試
- 使用@WebMvcTest進行測試
- 使用@DataJpa進行持久層測試
- 使用@JsonTest測試序列化
- 使用MockWebServer測試Spring WebClient Rest呼叫
- 使用@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標準的實現
引入
<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測試更加輕量級。
如果你是用Gson
和Jsonb
,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);
}