@valueを用いているクラスに対するモックを用いたテストのコツ

[東京理科大学 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/tokyo_university_of_science)の9日目です。

今回はJavaのフレームワークSpringにおいて `@Value`アノテーションを用いたクラスに対してテストをする際の方法/コツについて書いていきます。

### `@Value`とは

`@Value`アノテーションは別のファイルから値をインジェクションしたいときに用いるアノテーションです。
Spring Bootの場合デフォルトの設定のままですと、application.ymlまたはapplication.propertiesに記載してある値から読み取りインジェクションします。

具体的に書くとこのような形式です。
まず、application.ymlに値を書きます。

```yml:application.yml
sample.threshold : 1
```

そして、アノテーションの引数に

```
@Value("${先程のkey}")
```
としたうえで、クラス内のフィールドや引数などに`@Value`アノテーションを付与します。

具体的には

```java:HogeCoordinator.java
@Service
public class HogeCoordinator {

  @value("${sample.threshold}")
  private Integer threshold;

  @Autowired
  private HogeService hogeService;

  public void hoge() {
    hogeService.fuga(threshold);
  }
}

```

このようなイメージです。
HogeCoordinatorクラスのフィールドthresholdに`@value("${sample.threshold}")`と記載するとapplication.ymlに記載したのkeyから1を読み取り、アプリケーションを実行するとインジェクションしてくれます。

これにより、アプリケーション固有の値などを1ファイルに集めて管理できるのがメリットです。

### `@Value`を用いているクラスに対するテスト

それでは本題の`@Value`を用いているクラスに対するテスト方法です。

テスト対象は先程のHogeCoordinator.javaとします。
`@Value`を用いている場合、下記のような一般的なテストの書き方をしてしまうと、`@Value`を付与している対象に値をインジェクションしてくれません。

```java:FailedHogeCoordinatorTest.java
public class FailedHogeCoordinatorTest {

  @InjectMocks
  private HogeCoordinator hogeCoordinator;

  @Mock
  private HogeService hogeService;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
  }

  // 以下省略
}
```

そこで、ReflectionTestUtilsクラスのsetFieldメソッドを用います。メソッドの引数には対象のインスタンス、インジェクションを行うフィールド名、インジェクションする値を指定します。

このメソッドを用いるために、HogeCoordinatorのインスタンスを 生成しておく必要があるため、newしておきます。

これを踏まえてテストを書くとこのようになります。

```java:SuccessHogeCoordinatorTest.java
public class SuccessHogeCoordinatorTest {

  @InjectMocks
  private HogeCoordinator hogeCoordinator = new HogeCoordinator();

  @Mock
  private HogeService hogeService;

  @Before
  public void setup() {
    ReflectionTestUtils.setField(hogeCoordinator, "threshold", 1);
    MockitoAnnotations.initMocks(this);
  }

  // 以下省略
}
```

こうすることで、`@Value`を付与しているフィールドに値がインジェクションされます。

コツとしてはモックをインジェクションするクラスのインスタンスをテスト時に生成する必要があるため、DIを行う際にはコンストラクタインジェクションをしない方がよいということです。

今回のテスト対象であるHogeCoordinatorクラスは`@Autowired`を用いてDIしていますが、例えば下記のようにLombokの`@RequiredArgsConstructor`を用いてコンストラクタインジェクションをしてしまうと、コンストラクタの引数が増えてしまうため、インスタンスの生成が面倒になります。

```java:HogeCoordinator.java
@Service
@RequiredArgsConstructor
public class HogeCoordinator {

  @value("${sample.threshold}")
  private Integer threshold;

  private final HogeService hogeService;

  public void hoge() {
    hogeService.fuga(threshold);
  }
}

```

そのため、ReflectionTestUtilsクラスのsetFieldメソッドを用いてテストをするクラスにインジェクションする場合は、コンストラクタインジェクションは避けた方が楽になると考えます。

### まとめ
`@Value`を付与しているクラスに対してテストする際はReflectionTestUtilsクラスのsetFieldメソッドを用いると値をインジェクションしてくれます。
その際にテスト対象のクラスのDIはコンストラクタインジェクション以外の方法にしておいた方が、テストの際にインスタンスの生成が容易になり、楽にテストすることができます。

### さいごに

今回OBという縁がありまして、アドベントカレンダーに参加させていただくことにしました。
私自身初のアドベントカレンダーへの参加ということもあり、貴重な場を設けてくださった@piffettさんには感謝しております。
ありがとうございました。

### 参考文献

[Spring公式ドキュメント(@Value)](https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/core.html#beans-value-annotations) 

[Spring公式ドキュメント(General Testing Utilities)](https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/testing.html#unit-testing-support-classes)