きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

SpringのAssertクラスで簡単にValidationする【Java】

この記事はAssertクラスを紹介するだけの小ネタです。


Springには、Assertクラスという簡単にValidationしてくれるようなクラスがあります。複雑なことはできませんが、サクっとメソッドのパラメータを検証したい時は便利です。なお、複雑なことをしたければ、Apache Commons Langのorg.apache.commons.lang3.Validate, Google Guavaの Preconditions等のサードパーティを使うようにとAssertクラスのJavaDocに記載されています。

環境

  • Java
    • 15
  • org.springframework.boot
    • 2.4.5

使い方

org.springframework.utilパッケージのAssertクラスを使います。

色々とメソッドはありますが、state(boolean expression , String message)以外は特に使わないと思うので、紹介しません。第一引数に条件、第二引数にエラーメッセージを指定します。

内部的な処理はこれだけです。

public static void state(boolean expression, String message) {
  if (!expression) {
    throw new IllegalStateException(message);
  }
}

今回はnullチェックだけ行っていますが、数値の範囲指定や最大値等のチェックも行うことができます。

// 自作メソッド
public static void notNullValidate(Object obj) {
  Assert.state(obj != null, "param is null");
}

エラーメッセージを修正することができるので、細かくアレンジすることができます。

java.lang.IllegalStateException: param is null

    at org.springframework.util.Assert.state(Assert.java:76)
    at kirimaru.biz.domain.AssertPractice.notNullValidate(AssertPractice.java:13

ソースコード

終わりに

正直、小ネタも小ネタです。このメソッドを知らなくても、簡単にハンドリングできますからね。

Objects.requireNonNull()だとNullPointerExceptionが発生して、意図的なエラーか意図していないエラーかが分かりません。Assert.state()だとIllegalStateExceptionですので、そこの使い分けができるかもしれません。

アプリケーションの起動時のチェックとして使用すると、良いと思います。もし、アプリケーションの起動時チェックとして扱いたい時は、こちらの記事も参考にしてください。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

f:id:nainaistar:20210611000203p:plain

Wiremockをモックサーバとして使用してJavaの単体テストをする

弊社ではモックサーバにWiremockを使用しています。

この記事ではWiremockを知ってもらうきっかけを提供することを目的としています。詳しいことは他の方の記事を参考にした方が良いでしょう。

ゴール

  • WiremockのモックサーバでJUnitでテストする

Wiremockのスタンドアロンで起動する機能については、今回は紹介しません。スタンドアロンなら別のいいツールあるかもしれないので。

Wiremockとは

WireMockはHTTPモックサーバー。リクエスト回数やリクエストJSONを確認することもできる。

スタンドアロンとしても立ち上げることができるので、JUnitとの連携だけでなくE2Eテストや検証環境でも使用することができる。

環境

  • Java
    • 15
  • org.springframework.boot
    • 2.4.0
  • com.github.tomakehurst:wiremock-jre8
    • 2.27.2
  • ru.lanwen.wiremock:wiremock-junit5
    • 1.3.1

単体テストでの使い方

Wiremockが使えるように依存関係に含める

使用できるように依存関係に含めます。デフォルトだとJUnit4の記述しかできないので、JUnit5でも使用できるように関連ライブラリも依存関係に含めます。

Gradleでの記述方法。

testImplementation "com.github.tomakehurst:wiremock-jre8:2.27.2"
testImplementation "ru.lanwen.wiremock:wiremock-junit5:1.3.1"

Wiremockをテストクラスで使えるようにする

WiremockResolver.classを読み込みます。

@ExtendWith(WiremockResolver.class)

Wiremockサーバを初期セットアップする


テストクラスのコンストラクタを使用します。

@WiremockResolver.Wiremockをパラメータに付けることで、初期化対象となります。

ランダムポートで生成されるので、テストターゲットに対してパラメータを渡してあげることで並列実行等もできるようになります。

  private WireMockServer server;

  ZipCloudClientImplTests(@WiremockResolver.Wiremock WireMockServer server) {
    this.server = server;
  }
  @BeforeEach
  void setup() {
    ZipCloudClientProperties properties = new ZipCloudClientProperties();
    properties.setSchema("http");
    properties.setHost("localhost");
    properties.setPort(server.port());
    properties.setPath("/api/search");

    RestOperationFactory restOperationFactory = new RestOperationFactory(restTemplateBuilder, new RestTemplateInterceptor());
    RestOperations restOperations = restOperationFactory.createRestOperations(properties);

    target = new ZipCloudClientImpl(properties, restOperations);
  }

モックをセットする


あとはWiremockに設定するのみです。

こちらを設定したうえで、テストを実施すると対向システムをモックサーバとした状態でテストできます。

次の記載方法は、特定のURLとクエリパラメータ時に、特定のResponseBodyを返却する書き方となります。

    String responseBody =
        """
            {
              "message":null,
              "results": [{
                "address1":"神奈川県",
                "address2" : "厚木市",
                "address3" : "中町",
                "kana1" : "カナガワケン",
                "kana2" : "アツギシ",
                "kana3" : "ナカチョウ",
                "prefcode" : "14",
                "zipcode" : "2430018"
              }],
              "status": 200
            }""";

    server.stubFor(
        get("/api/search?zipcode=2430018").willReturn(
            aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "text/plain")
                .withBody(responseBody)
        )
    );

リクエストJSONを比較したい時には、withRequestBodyを使用します。※ソースコードでは比較していません。

    String requestBody =
        """
            {
              "message":null,
              "results": [{
                "address1":"神奈川県",
                "address2" : "厚木市",
                "address3" : "中町",
                "kana1" : "カナガワケン",
                "kana2" : "アツギシ",
                "kana3" : "ナカチョウ",
                "prefcode" : "14",
                "zipcode" : "2430018"
              }],
              "status": 200
            }""";

    server.stubFor(
        post("/api/search")
            .withRequestBody(
                equalToJson(requestBody)
            )

ソースコード

終わりに

Wiremockが使えると単体テストができるようになるので便利です。

もちろん、できるだけ本番環境でテストできることが最高ですが、相手のサービスのメンテナンス時間が原因でテスト失敗するFlakyなテストになってしまいます。実行するたびに課金されるようなAPIを実行しても厳しいですしね。

知らないだけでもっと便利なJavaのモックサーバはあるかもしれませんが、Spring公式でも紹介されている以上、便利なものでしょう。

JavaとWiremockで調べる人が多いとは思いませんが、一助になればと思います。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考

Wiremock公式サイト wiremock.org

Spring公式のWiremockの使用方法 cloud.spring.io

f:id:nainaistar:20211009232958p:plain

Javaでカンマ区切りの文字列を作る(StringJoiner, StringBuilder, String)

小ネタ。JavaのStringJoinerを使うことで、カンマ区切りの文字列を簡単に作れます。

今回の記事は、StringBuilderを使ったカンマ区切りの文字列の作り方をメモします。既存のStringBuilderでのカンマ区切りの文字列を作り方も併記します。

環境

  • Java
    • 15

クラスの登場時期

  • StringBuilder
    • 1.5
  • StirngJoiner
    • 1.8

StirngJoinerはJava8から導入されているので、多くのプロジェクトで使えると思います。

ゴール

  • 配列の「りんご」、「ゴリラ」、「ラッパ」を元に「りんご,ゴリラ,ラッパ」の文字列を作成する
  • StringJoinerの使い方が分かる

StringJoinerでの構築方法

StringJoinerの第一引数は区切り文字を指定します。今回は区切り文字にカンマを指定します。

public String joiner(List<String> words) {

  var sj = new StringJoiner(",");
  for (String word : words) {
    sj.add(word);
  }
  return sj.toString();
}

StringBuilderでの構築方法

実装方式は色々ありますが、大きくは2つの方式があります。

1つは配列の文字を取り出すたびに文字とカンマを追加し、処理の最後に余分なカンマを削除する方法です。

public String builder(List<String> words) {
  var sb = new StringBuilder();
  for (String word : words) {
    sb.append(word);
    sb.append(",");
  }
  // カンマを削除
  sb.delete(sb.length() - 1, sb.length());

  return sb.toString();
}

1つは配列の文字を取り出し、最後の配列の文字以外にカンマを追加する方法です。

public String builder2(List<String> words) {
  var sb = new StringBuilder();
  for (int i = 0; i < words.size(); i++) {
    sb.append(words.get(i));
    // 最後の配列の文字以外はカンマを追加する
    if (words.size() - 1 != i) {
      sb.append(",");
    }
  }

  return sb.toString();
}

処理自体は簡単ですが、StringJoinerと比べると一手間を加える必要があります。当然記述が少ない方がエラーが発生しづらいので、StringJoinerを使える環境であればStringJoinerを使いましょう。

その他のStringJoinerの挙動

nullを投入した時の挙動について

StringJoinerにnullを追加した場合、返却値は残念ながら"null"となります。Java8で導入された比較的あたらしいクラスですが、誤って「null様」を表示しないように注意しましょう。

@Test
void test_05() {
  List list = new ArrayList();
  list.add(null);
  assertThat(
      target.tab(list)
  ).isEqualTo("null");
}

カンマ以外の区切り文字について

区切り文字はなんでも定義できます。タブ文字でも、1文字以外でもできます。

public String tab(List<String> words) {
  var sj = new StringJoiner("\t");
  for (String word : words) {
    sj.add(word);
  }
  // りんご ゴリラ   ラッパ
  return sj.toString();
}

public String addAiueo(List<String> words) {
  var sj = new StringJoiner("AIUEO");
  for (String word : words) {
    sj.add(word);
  }
  // りんごAIUEOゴリラAIUEOラッパ
  return sj.toString();
}

結合した文字の前後を囲みたい

StringJoiner(String delimiter)だけでなく、StringJoiner(String delimiter, String prefix, String suffix)というメソッドがあります。

最終的に結合した文字をカッコで囲んだり、ブラケットで囲みたい時にはこちらを使うと便利です。

public String addBracket(List<String> words) {
  var sj = new StringJoiner(",", "{", "}");
  for (String word : words) {
    sj.add(word);
  }
  // {りんご,ゴリラ,ラッパ}
  return sj.toString();
}

もっと簡単に使いたい

Java8からStringにjoin()が追加されています。前後を何かで囲みたい、という要望が無ければ、こちらを使ったほうが簡単には書けます。

String.join(",", param);

内部的にはStringJoinerを使っているので、特に特別なことはしていません。

public static String join(CharSequence delimiter,
        Iterable<? extends CharSequence> elements) {
    Objects.requireNonNull(delimiter);
    Objects.requireNonNull(elements);
    StringJoiner joiner = new StringJoiner(delimiter);
    for (CharSequence cs: elements) {
        joiner.add(cs);
    }
    return joiner.toString();
}

できないこと

追加文字自体の前後に何かを加えたい

文字全体を指定した文字で囲むことはできますが、追加した文字を追加しながら囲むことができません。

例えば次のように、全体をブラケットで囲みつつ、追加した文字をダブルクォーテーションで加えたいとなるとStringJoinerだけでは上手くいきません。MessageFormatを使ったり、プラスで結合する等で工夫してください。

public String addBracketAndDoubleQuote(List<String> words) {
  var sj = new StringJoiner(",", "{", "}");
  for (String word : words) {
    sj.add(MessageFormat.format("\"{0}\"", word));
  }
  // {"りんご","ゴリラ","ラッパ"}
  return sj.toString();
}
  

// MyBatisのSQLを動的に生成するときのやり方で使う
sj = new StringJoiner(",", "(", ")");
for (String word : words) {
  sj.add("#{" + word + "}");
}
// (#{りんご}, #{ゴリラ}, #{ラッパ})

ソースコード

終わりに

元々StringJoinerのことは知っていましたが、やはり便利ですね。

この検証中にStirngのjoinメソッドを知ることができたのは大きな収穫でした。元々は、「配列なら次の書き方でもカンマ区切りの文字列を取得できます。」と紹介しようと思っていたのに、IntelliJ IDEAからのサジェスチョンがあり気付くことができました。

IDEが知らないことを教えてくれるのは成長を感じられて良いですね。

Stream.of("りんご", "ゴリラ", "ラッパ").collect(Collectors.joining(","));

今後も知っていることはブログにしつつ、ローカルで検証することで知識量を増やしていきたいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似

f:id:nainaistar:20210617225151p:plain