Help us understand the problem. What is going on with this article?

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

東京理科大学 Advent Calendar 2019の9日目です。

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

@Valueとは

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

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

application.yml
sample.threshold : 1

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

@Value("${先程のkey}")

としたうえで、クラス内のフィールドや引数などに@Valueアノテーションを付与します。

具体的には

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を付与している対象に値をインジェクションしてくれません。

FailedHogeCoordinatorTest.java
public class FailedHogeCoordinatorTest {

  @InjectMocks
  private HogeCoordinator hogeCoordinator;

  @Mock
  private HogeService hogeService;

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

  // 以下省略
}

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

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

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

SuccessHogeCoordinatorTest.java
public class SuccessHogeCoordinatorTest {

  @InjectMocks
  private HogeCoordinator hogeCoordinator = new HogeCoordinator();

  @Mock
  private HogeService hogeService;

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

  // 以下省略
}

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

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

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

HogeCoordinator.java
@Service
@RequiredArgsConstructor
public class HogeCoordinator {

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

  private final HogeService hogeService;

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

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

まとめ

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

さいごに

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

参考文献

Spring公式ドキュメント(@Value)

Spring公式ドキュメント(General Testing Utilities)

rebi
エンジニア2年生/サーバーサイドエンジニア /SpringBoot, DDD
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした