前提
- Java
- Spring boot
- Jacksonを利用してobject <-> json変換
- junitで単体テストを書く
- lombokを利用している
概要
Javaでunittestを書くとき、テストで使うデータを用意するのはわりとめんどくさい。
var hoge = new Hoge("hogeId", "hogeName", new Fuga("fugaId", "fugaName", ...));
というわけで、一般的には外部にjsonファイルを用意し、これを読み込ませてテストに使う。
- hoge.json
{
"id": "hogeId",
"name": "hogeName",
"fuga": {
"id": "fugaId",
"name": "fugaName",
...
}
}
しかし、一部のオブジェクト(たとえばドメインモデル)は「不変オブジェクト(immutable object)」にしないといけない。そういうコーディングルールはよくある。
@Builder
@Getter
public class Hoge {
private final String id;
private final String name;
private final Fuga fuga;
}
これをObjectMapperを使って読み込むことができるか?
実装
もちろん可能。
@JsonDeserialize(builder=Hoge.HogeBuilder.class)
@Builder
@Getter
public class Hoge {
private final String id;
private final String name;
private final Fuga fuga;
@JsonPOJOBuilder(withPrefix="")
public static class HogeBuilder {}
}
これで、
@Test
public void testHoge() throws IOException {
var fixture = getClass().getResource("./hoge.json");
var hoge = mapper.readValue(fixture, Hoge.class);
...
}
いつも通りjson fixtureを使ったテストができる。
参照
テストのためにモデルにアノテーションを付与することがコーディングルール上許されるか?の問題は残るが・・・。
おおっと(余談)
話はこれで終わらないことが判明。
@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
private final String id;
private final String name;
private final Hoge hoge;
@JsonPOJOBuilder(withPrefix="")
public static class FugaBuilder {}
}
@Builder
@Getter
// value object
public class Hoge {
private final String hoge;
}
こういった「文字列を含むプリミティブ型一属性しかもたないValueObjectは、ObjectMapperは特別扱いする」ことが分かった。
こういったimmutableな一属性モデルを、ObjectMapperはデフォルトでDDDのvalue objectとして扱う。
{
"id": "fugaId",
"name": "fugafuga",
"hoge": {
"hoge": "hogehoge"
}
}
これはエラーになり、
{
"id": "fugaId",
"name": "fugafuga",
"hoge": "hogehoge"
}
これだと通る。こういうjson階層を無視するような動きをjacksonはDelegatingと言うらしい。つまり
@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
private final String id;
private final String name;
private final Hoge hoge;
@JsonPOJOBuilder(withPrefix="")
public static class FugaBuilder {}
}
@Builder
@Getter
// value objectチックだが、primitiveじゃない
public class Hoge {
private final Hogehoge hoge;
}
@Builder
@Getter
public class Hogehoge {
private final String id;
private final String name;
}
こんなモデルは
{
"id": "fugaId",
"name": "fugafuga",
"hoge": {
"hoge": {
"id": "hogeId",
"name": "hogeId"
}
}
}
こんなjsonになるが、delegatingしたいなら
@Builder
@Getter
@JsonDeserialize(builder=Fuga.FugaBuilder.class)
public class Fuga {
private final String id;
private final String name;
private final Hoge hoge;
@JsonPOJOBuilder(withPrefix="")
public static class FugaBuilder {}
}
@Builder
@Getter
public class Hoge {
private final Hogehoge hoge;
// Delegatingを付与できるのが@JsonCreator以外見つからず。
@JsonCreator(mode=JsonCreator.Mode.Delegating)
public static Hoge newHoge(Hogehoge hogehoge){
return Hoge.builder().hoge(hogehoge).build();
}
}
@Builder
@Getter
@JsonDeserialize(builder=Hogehoge.HogehogeBuilder.class)
public class Hogehoge {
private final String id;
private final String name;
@JsonPOJOBuilder(withPrefix="")
public static class HogehogeBuilder {}
}
こんなコードを書いてあげるとjson階層が繰り上がって
{
"id": "fugaId",
"name": "fugafuga",
"hoge": {
"id": "hogeId",
"name": "hogeId"
}
}
こうなる。jacksonはまだまだ深い・・・・