unit-test-json-serialization
JacksonおよびSpring Bootの`@JsonTest`を使ったJSONシリアライズ/デシリアライズのユニットテストパターンを提供します。JSONマッピング、カスタムシリアライザー、日付フォーマット、ポリモーフィック型の検証に対応しています。JSONシリアライズのテスト作成やカスタムシリアライザーの検証が必要な際に活用してください。
description の原文を見る
Provides patterns for unit testing JSON serialization/deserialization with Jackson and `@JsonTest`. Validates JSON mapping, custom serializers, date formats, and polymorphic types. Use when testing JSON serialization, validating custom serializers, or writing JSON unit tests in Spring Boot applications.
SKILL.md 本文
@JsonTest を使用した JSON シリアライゼーション のユニットテスト
概要
Spring の @JsonTest と Jackson を使用した JSON シリアライゼーション とデシリアライゼーション のユニットテストパターンを提供します。POJO マッピング、カスタムシリアライザー、フィールド名マッピング、ネストされたオブジェクト、日付/時刻フォーマット、ポリモーフィック型をカバーしています。
使用時機
- DTO の JSON シリアライゼーション/デシリアライゼーション のテスト
- カスタム Jackson シリアライザー/デシリアライザーの検証
@JsonProperty、@JsonIgnore、フィールド名マッピングの検証- 日付/時刻フォーマットの処理テスト (LocalDateTime, Date)
- null ハンドリングと欠落フィールドのテスト
- ポリモーフィック型デシリアライゼーション のテスト
手順
- テストクラスを
@JsonTestでアノテーション → JacksonTester の自動構成を有効化 - 対象型に JacksonTester をオートワイア → タイプセーフな JSON アサーション を提供
- シリアライゼーション のテスト →
json.write(object)を呼び出し、extractingJsonPath*で JSON パスをアサート - デシリアライゼーション のテスト →
json.parse(json)またはjson.parseObject(json)を呼び出し、オブジェクトの状態をアサート - ラウンドトリップを検証 → シリアライズ してからデシリアライズ し、データが同じであることを確認 (オブジェクトが適切に比較可能な場合)
- エッジケースをテスト → null 値、欠落フィールド、空のコレクション、無効な JSON
- 検証チェックポイントを追加: 各アサーション 後、誤ったデータで有意義にテストが失敗することを確認
例
Maven セットアップ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Gradle セットアップ
dependencies {
implementation("org.springframework.boot:spring-boot-starter-json")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
基本的なシリアライゼーション とデシリアライゼーション
@JsonTest
class UserDtoJsonTest {
@Autowired
private JacksonTester<UserDto> json;
@Test
void shouldSerializeUserToJson() throws Exception {
UserDto user = new UserDto(1L, "Alice", "alice@example.com", 25);
JsonContent<UserDto> result = json.write(user);
result
.extractingJsonPathNumberValue("$.id").isEqualTo(1)
.extractingJsonPathStringValue("$.name").isEqualTo("Alice")
.extractingJsonPathStringValue("$.email").isEqualTo("alice@example.com")
.extractingJsonPathNumberValue("$.age").isEqualTo(25);
}
@Test
void shouldDeserializeJsonToUser() throws Exception {
String json_content = "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"age\":25}";
UserDto user = json.parse(json_content).getObject();
assertThat(user.getId()).isEqualTo(1L);
assertThat(user.getName()).isEqualTo("Alice");
assertThat(user.getEmail()).isEqualTo("alice@example.com");
assertThat(user.getAge()).isEqualTo(25);
}
@Test
void shouldHandleNullFields() throws Exception {
String json_content = "{\"id\":1,\"name\":null,\"email\":\"alice@example.com\"}";
UserDto user = json.parse(json_content).getObject();
assertThat(user.getName()).isNull();
}
}
カスタム JSON プロパティ
public class Order {
@JsonProperty("order_id")
private Long id;
@JsonProperty("total_amount")
private BigDecimal amount;
@JsonIgnore
private String internalNote;
}
@JsonTest
class OrderJsonTest {
@Autowired
private JacksonTester<Order> json;
@Test
void shouldMapJsonPropertyNames() throws Exception {
String json_content = "{\"order_id\":123,\"total_amount\":99.99}";
Order order = json.parse(json_content).getObject();
assertThat(order.getId()).isEqualTo(123L);
assertThat(order.getAmount()).isEqualByComparingTo(new BigDecimal("99.99"));
}
@Test
void shouldIgnoreJsonIgnoreFields() throws Exception {
Order order = new Order(123L, new BigDecimal("99.99"));
order.setInternalNote("Secret");
assertThat(json.write(order).json).doesNotContain("internalNote");
}
}
ネストされたオブジェクト
public class Product {
private Long id;
private String name;
private Category category;
private List<Review> reviews;
}
@JsonTest
class ProductJsonTest {
@Autowired
private JacksonTester<Product> json;
@Test
void shouldSerializeNestedObjects() throws Exception {
Product product = new Product(1L, "Laptop", new Category(1L, "Electronics"));
JsonContent<Product> result = json.write(product);
result
.extractingJsonPathNumberValue("$.category.id").isEqualTo(1)
.extractingJsonPathStringValue("$.category.name").isEqualTo("Electronics");
}
@Test
void shouldDeserializeNestedObjects() throws Exception {
String json_content = "{\"id\":1,\"name\":\"Laptop\",\"category\":{\"id\":1,\"name\":\"Electronics\"}}";
Product product = json.parse(json_content).getObject();
assertThat(product.getCategory().getName()).isEqualTo("Electronics");
}
@Test
void shouldHandleListOfNestedObjects() throws Exception {
String json_content = "{\"id\":1,\"reviews\":[{\"rating\":5},{\"rating\":4}]}";
Product product = json.parse(json_content).getObject();
assertThat(product.getReviews()).hasSize(2);
}
}
日付/時刻フォーマット
@JsonTest
class DateTimeJsonTest {
@Autowired
private JacksonTester<Event> json;
@Test
void shouldFormatDateTimeCorrectly() throws Exception {
LocalDateTime dt = LocalDateTime.of(2024, 1, 15, 10, 30, 0);
json.write(new Event("Conference", dt))
.extractingJsonPathStringValue("$.scheduledAt").isEqualTo("2024-01-15T10:30:00");
}
}
カスタムシリアライザー
public class CustomMoneySerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value == null ? null : String.format("$%.2f", value));
}
}
@JsonTest
class CustomSerializerTest {
@Autowired
private JacksonTester<Price> json;
@Test
void shouldUseCustomSerializer() throws Exception {
json.write(new Price(new BigDecimal("99.99")))
.extractingJsonPathStringValue("$.amount").isEqualTo("$99.99");
}
}
ポリモーフィックデシリアライゼーション
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = CreditCard.class, name = "credit_card"),
@JsonSubTypes.Type(value = PayPal.class, name = "paypal")
})
public abstract class PaymentMethod { }
@JsonTest
class PolymorphicJsonTest {
@Autowired
private JacksonTester<PaymentMethod> json;
@Test
void shouldDeserializeCreditCard() throws Exception {
String json_content = "{\"type\":\"credit_card\",\"id\":\"card123\"}";
assertThat(json.parse(json_content).getObject()).isInstanceOf(CreditCard.class);
}
@Test
void shouldDeserializePayPal() throws Exception {
String json_content = "{\"type\":\"paypal\",\"id\":\"pp123\"}";
assertThat(json.parse(json_content).getObject()).isInstanceOf(PayPal.class);
}
}
ベストプラクティス
- シリアライゼーション とデシリアライゼーション の両方をテストして完全なカバレッジを確保
- JSON 全体を比較するのではなく、JSON パスを個別に検証
- null ハンドリングを明示的にテスト — null フィールドは
@JsonIncludeに応じて含まれたり除外されたりする場合があります extractingJsonPath*メソッドを使用してフィールドアサーション を精密に実行- ラウンドトリップをテスト: オブジェクトをシリアライズ し、JSON をデシリアライズ し、結果が一致することを確認
- エッジケースを検証: 空の文字列、空のコレクション、深くネストされた構造
- 関連するアサーション を単一のテストにグループ化して明確性を向上
制約と警告
@JsonTest限定的なコンテキスト読み込み: JSON 関連のビーンのみ。完全な Spring コンテキストには@SpringBootTestを使用してください- Jackson バージョン: アノテーションバージョンが使用中の Jackson バージョンと一致していることを確認
- 日付フォーマット: ISO-8601 がデフォルト。カスタムパターンには
@JsonFormatを使用 - Null ハンドリング:
@JsonInclude(Include.NON_NULL)を使用して null をシリアライゼーション から除外 - 循環参照: 無限ループを防ぐために
@JsonManagedReference/@JsonBackReferenceを使用 - イミュータブルオブジェクト: コンストラクタベースのデシリアライゼーション に
@JsonCreator+@JsonPropertyを使用 - ポリモーフィック型:
@JsonTypeInfoは デシリアライゼーション が機能するためにサブタイプを正しく識別する必要があります
デバッグワークフロー
JSON テストが失敗した場合は、このワークフローに従ってください:
| 失敗の兆候 | 一般的な原因 | 確認方法 |
|---|---|---|
JsonPath アサーション 失敗 | フィールド名の不一致 | @JsonProperty のスペルが JSON キーと一致しているか確認 |
| Null が期待されたが値を取得 | @JsonInclude(NON_NULL) 設定済み | フィールド/クラスのアノテーション を確認 |
| デシリアライゼーション で間違った型を返す | @JsonTypeInfo 欠落 | JSON に型情報プロパティを追加するか、サブタイプマッピングを設定 |
| 日付フォーマット不一致 | フォーマット文字列が不正 | @JsonFormat(pattern=...) が期待される文字列と一致していることを確認 |
| 出力でフィールドが欠落 | @JsonIgnore または transient 修飾子 | フィールドに @JsonIgnore または transient キーワードがあるか確認 |
| ネストされたオブジェクトが null | 内側の JSON が欠落または不正形式 | 解析された JSON をログ出力。内側の構造が POJO と一致することを確認 |
JsonParseException | JSON 文字列が不正形式 | JSON 構文を検証。エスケープ されていない文字を確認 |
修正後の検証チェックポイント: テストを再実行 — パスしたら、対比するテストを記述してください (例: null ハンドリングを修正したら、非 null 値のテストを追加して回帰を防止)。
リファレンス
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- giuseppe-trisciuoglio
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/giuseppe-trisciuoglio/developer-kit / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。