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