Junitでモックを使ってみよう!
IT技術
はじめに
現在、所属しているプロジェクトではSpring Bootを使用したAPI開発と、Junitを利用したユニットテストを実施しています。
ユニットテストをしていると、依存しているオブジェクトがまだ未完成でテストができないよー、という状況に一度は遭遇されたことがあるのではないでしょうか。
そういった時にモックを使ってテストを実施する方法を簡単に紹介したいと思います。
Junitとは
JUnitとは、Java言語で開発されたプログラムの単体テスト(ユニットテスト)を行なうためのソフトウェア。また、そこで用いられるテストコードの記述体系を含むテストフレームワーク。
Junitバージョン
Junit5.7
モックやスタブとは?
モックに似ている言葉としてスタブがあります。モックもスタブも意味が似ていて混乱したのでまずはモック、スタブの各用語の意味についてネットで調べて整理してみました。
スタブ
依存するオブジェクトの代用となるオブジェクトのことです。テストではスタブに任意の戻り値を設定して、予測可能な動作をするようにして使います。
モック
モックは依存しているオブジェクトのメソッドの呼び出し回数の検証など、依存オブジェクトが正しく利用されているかの検証をするのが主な目的のようです。一方でスタブの機能を併せ持っていることがあるらしく、依存するオブジェクトの代用としての意味も持っているようでした。
Junitにおけるモック、スタブ
Junit単体ではモック、スタブ機能は提供されていないので、外部のライブラリを利用します。ライブラリは色々あるみたいですが、Mockitoライブラリを利用する方法が一般的のようです。
Mockitoではモック機能はスタブ機能の上位互換として提供されています。そのため、まずはどちらを利用するにしても後述するMockitoクラスのmockメソッドを使いモックオブジェクトを作成して、モックオブジェクトをモック、スタブとして利用することになります。
モック、スタブのどちらの場合も後述するmockメソッドで作成しますが、メソッド呼び出しの回数検証などを実施するならばモックとして機能して、メソッドの戻り値などを設定して代用として利用するならば、スタブとして機能するイメージです。
私の感覚だとJunitに限らずスタブよりモックという言葉がより使われていて、また、モックをスタブ的な意味で使われていることが多いかなと感じています。本稿ではモック、モックオブジェクトを「まだ完成していない機能の代用」というスタブ的な意味としても使おうと思います。
Mockitoを使用してモックを使ってみる
モックに予測可能な値を戻すメソッドを定義し、未完成のオブジェクトの代用として使用することでテストができるようになります。
Junitでモックオブジェクトの作成、メソッドの定義、モックの設定、モックしたメソッドの検証方法について説明します。
モックオブジェクトの作成
org.mockito.Mockitoクラスのmockメソッドを用いてモックオブジェクトを作成して、引数にはモックしたいクラスを指定します。mockメソッドを用いて作成されたモックオブジェクトは全てのメソッドがnullを返すメソッドとして設定されます。テストでは適切にモックしたい各メソッドの戻り値を設定しておく必要があります。
以下はListをモック化した例です。
※ 一部のメソッドのみ定義を変えたい場合はspyを利用する必要があります
1import static org.mockito.Mockito.*
2
3// モックであり、スタブ
4List<String> mockObject = mock(List.class)
モックオブジェクトのメソッドの定義
モックに予測可能な値を戻す(戻り値、例外)メソッドを定義する方法を説明します。
2通りの記法
メソッドをモック化する記法は前置記法と後置記法の2通りがあります。どちらを利用するかですが、後置記法では利用できないパターンがあるので、前置記法を利用したほうがよさそうです。
私が所属しているプロジェクトでも前置記法で統一していました。
1// モック化
2モックしたいクラス mockObject = mock(モックしたいクラス)
3
4// 前置記法
5doReturn("戻り値").when(mockObject).MethodA();
6doThrow(new Exception()).when(mockObject).MethodA();
7
8// 後置記法
9when(mockObject).thenReturn("戻り値");
10when(mockObject).thenThrow(new Exception());
以下、後置記法では利用できないこと
- 戻り値Voidのメソッドを定義できない
- スパイオブジェクトでは利用できない
戻り値の設定
モックオブジェクトを作成したらモックしたいメソッドの戻り値を設定します。メソッドの指定した引数で呼び出された時の戻り値を設定します。anyInt()やanyString()はメソッドの引数の型定義に従って設定する必要があります。
1// モック化
2モックしたいクラス mockObject = mock(モックしたいクラス)
3
4// methodAが引数に何か値を設定して呼び出されたときの戻り値をnullにしている
5doReturn(null).when(mockObject).methodA(any());
6
7// methodBが2つの引数に何か値を設定して呼び出されたときの戻り値を"文字列"にしている
8doReturn("文字列").when(mockObject).methodB(any(), any());
9
10// methodCが2つの引数に(Int、String)を設定して呼び出されたときの戻り値を2にしている
11doReturn(2).when(mockObject).methodC(anyInt(), anyString());
例外の設定
実際に例外を発生させる条件が複雑な場合でも例外を発生させるようにモックを設定することで、簡単に例外のテストを試すことができます。
1// モック化
2モックしたいクラス mockObject = mock(モックしたいクラス)
3
4// methodAが引数に何か値を設定して呼び出された時にException例外を発生させる
5doThrow(new Exception()).when(
一部のメソッドをモックしたい場合
mockメソッドを使用した場合、全てのメソッドの戻り値がnullに設定されています。そこで、一部のメソッドのみをモックしたい時はspyメソッドを利用して一部だけのメソッドの戻り値を定義することが可能です。
1// モック化(スパイオブジェクト)
2モックしたいクラス mockObject = spy(モックしたいクラス);
3
4// MethodAだけ戻り値を設定できる
5doReturn("文字列").when(mockObject).MethodA(any(), any());
テスト対象クラスへのモックオブジェクトの設定
あとはテスト対象クラスのフィールドに依存しているオブジェクトに変わってモックオブジェクトを設定します。テストを実行すると定義したモックオブジェクトの予測可能なメソッドが実行されるようになります。
1/* テスト対象クラス */
2@Autowired
3private ClassA target
4・
5・
6// モック化
7モックしたいクラス mockObject = mock(モックしたいクラス)
8
9// メソッド定義
10doReturn(null).when(mockObject).methodA(any());
11
12ReflectionTestUtils.setField(
13 target,
14 "フィールド名",
15 mockObject
16);
verifyメソッドによる検証
テスト対象クラスにモック化したオブジェクトを設定して、テストを実施します。テストではクラスの仕様が満たされていることを確認しつつ、モック化したオブジェクトがちゃんと動作しているかの検証をします。
以下ではメソッド呼び出し回数を検証しています。
1// モック化
2モックしたいクラス mockObject = mock(モックしたいクラス)
3
4// メソッドの定義
5doReturn("文字列").when(mockObject).MethodA(any(), any());
6
7// 検証
8// 呼ばれていないことの検証
9verify(classA, never()).methodA(any())
10// methodAが1回呼ばれあていることの検証
11verify(classA, times(1)).methodA(any())
アノテーションを利用したモックオブジェクト
Spring Bootを使用したアプリケーションのテストでよく使用するモック関連のアノテーションについて簡単に紹介します。
@Mockと@Spy
Mockitoから提供されるアノテーションで、mockメソッド、spyメソッドの省略形です。フィールドのモック化するオブジェクトに@Mockや@Spyを指定します。@InjectMocksアノテーションでテスト対象のクラスに対してモック化したオブジェクトをインジェクションします。
1public class SampleTestA {
2
3// モック化
4@Mock
5ServiceA serviceA;
6
7// モック化(スパイオブジェクト)
8@Spy
9ServiceB serviceB;
10
11// テスト対象クラス
12// TargetTestClassのフィールドにモック化したオブジェクトをインジェクションする
13@InjectMocks
14private TargetTestClass target;
15・
16・
17
18}
@MockBeanと@SpyBean
Spring Bootが提供しているモック。
上述の@Mock、@Spyと同じような使い方をしますが、大きな違いはアプリケーションコンテキスト(DIコンテナ)にオブジェクトが登録されることです。
さいごに
以上、Junitを使用したモックの利用方法について簡単ではありますが紹介させて頂きました。
テストするクラスが他のオブジェクトに依存していて、依存先のオブジェクトがまだ未完成だという状況は往々にしてあると思いますが、モックを利用すると依存しているオブジェクトの有無にかかわらずユニットテストを実施することができるようになります。
この記事がこれからJunitを利用してテストする方のお役に立てれば幸いです。
最後までご覧頂きまして、ありがとうございました。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
おすすめ記事
immichを知ってほしい
2024.10.31