JUnit5の@ParameterizedTestで行われる引数の型変換

## はじめに
[ひとりJUnitアドベントカレンダー](https://qiita.com/advent-calendar/2022/junit)5日目の記事です。
5日目にして息切れしてきました。ひとりカレンダーしてる人すごい。時間がアホほど溶ける。

## `@ParameterizedTest`で受け渡される引数の型
この記事が載るアドベントカレンダーの2日目〜4日目までで、
`@ParameterizedTest`のさまざまな使い方、特に引数の渡し方を見てきました。

```java
  @ParameterizedTest
  @ValueSource(ints = {0, 1, 100, 1000})
  void test(int number) {
    int actual = hogeService.roundDown(number);
    assertThat(actual, is(0));
  }
```

上記の例で言えば、`@ValueSource`で指定している4つの数値が引数`number`に設定され、
1つのテストコードで4回分のテストが実行されます。

`@ValueSource`で`ints`と指定して、引数でも`int`を受け取っていますが
必ずしもSource側で指定されている型のみしか受け取れないわけではありません。
`String`を渡した場合、JUnitが提供する仕組みにより別の型として受け取ることができます。

## 基本的な型変換

まずはenumです。

```java
  @AllArgsConstructor
  @ToString
  private enum Nen {

    TEN("纏"),
    ZETSU("絶"),
    REN("練"),
    HATSU("発");

    private String kanji;
  }

  @ParameterizedTest
  @ValueSource(strings = {"TEN", "ZETSU", "REN", "HATSU"})
  void test(Nen nen) {
    System.out.println(nen);
  }
```

定義名(要するにenumの`name()`の戻り値)を指定し、
引数の型としてenumのクラス名を指定すると、その定義を受け取ることができます。

実行結果は以下の通り。しっかりenumとして受け取れています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541075/27931c5f-8473-afc9-386d-f027943ba087.png)

また、プリミティブ型も変換できます。

```java
  @ParameterizedTest
  @ValueSource(strings = {"true", "false"})
  void test(boolean isFoo) {
    System.out.println(isFoo);
  }
```

が、以下のように書けばよいだけなので、こちらを使うことはほぼないでしょう。

```java
  @ParameterizedTest
  @ValueSource(booleans = {true, false})
  void test(boolean isFoo) {
    System.out.println(isFoo);
  }
```

以下のようなたまに使ったり使わなかったりするクラスにも変換できます。

```java
  @ParameterizedTest
  @ValueSource(strings = {"2022-12-05", "2023-01-05"})
  void test(LocalDate localDate) {
    System.out.println(localDate);
  }

  @ParameterizedTest
  @ValueSource(strings = "https://qiita.com/advent-calendar/2022")
  void test(URI uri) {
    System.out.println(uri);
  }

  @ParameterizedTest
  @ValueSource(strings = {"en", "ja"})
  void test(Locale locale) {
    System.out.println(locale);
  }
```

全ラインナップはこちら。

https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion

## staticファクトリメソッドによる型変換
**staticファクトリメソッド**とは、そのクラスのインスタンスを返すstaticメソッドのことです。
例えば以下のようなもの。

```java
  @AllArgsConstructor
  @ToString
  public class Student {
    private String name;

    public static Student of(String name) {
      return new Student(name);
    }
  }
  
  public void hoge() {
    Student student = Student.of("John");
  }
```

Javaのパッケージで言うと、`LocalDateTime#of`や`Boolean#valueOf`などがそうです。
メソッド名は`of`や`valueOf`の他、`from`や`getInstance`等もよく見られます。

JUnit5では、`String`の引数一件を受け取るstaticファクトリメソッドがある場合
自動的にそのメソッドを使って型変換を行うことができます。

```java
  @AllArgsConstructor
  @ToString
  public class Student {
    private String name;

    public static Student of(String name) {
      return new Student(name);
    }
  }

  @ParameterizedTest
  @ValueSource(strings = {"Bob", "Tom"})
  void test(Student student) {
    System.out.println(student);
  }
```

じゃあ`String`を受け取るstaticファクトリメソッドが二つあったらどうなるの？ということで
以下のテストを実行してみました。

```java
  @ToString
  @Setter
  @NoArgsConstructor
  public class Student {
    private String name;

    public Student(String name) {
      System.out.println("constructor");
      this.name = name;
    }

    public static Student of(String name) {
      Student student = new Student();
      student.setName(name);
      System.out.println("of");
      return student;
    }

    public static Student valueOf(String name) {
      Student student = new Student();
      student.setName(name);
      System.out.println("valueOf");
      return student;
    }
  }

  @ParameterizedTest
  @ValueSource(strings = {"Bob", "Tom"})
  void test(Student student) {
    System.out.println(student);
  }
```

引数ありコンストラクタとstaticファクトリメソッド二種にsysoutを仕込んでいます。
さて、出力されるのは`of`か、それとも`valueOf`なのか、どちらかというと・・・

**実は`constructor`です。**

というのも、**staticファクトリメソッドが2種類あるとどちらも無視される**という仕様があり
またstaticファクトリメソッドが無くても、引数が`String`一つのコンストラクタがあれば
そちらを元に暗黙の型変換が行われるようになっているようなのです。

整理するとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541075/aa0d3d41-7a71-14ea-472c-f96f6c03df2b.png)
staticファクトリメソッドの方がコンストラクタよりも優先度は高い。
ただ、メソッドが2件以上あると無視される。ということですね。