LoginSignup
79
87

More than 5 years have passed since last update.

Mockitoを使ってみる。

Last updated at Posted at 2019-02-19

Mockitoとは

  • JUnitと同じくテストを自動化するためのライブラリ
  • モックを使用するテストを「モックテスト」と呼ぶ。
  • モック 伊藤

Mockitoのメリット

サイトを浅り読んだ限りでは以下のことが挙げられるそうだ。

  • テストのためにテスト対象クラスを書き換えるという行為を避けることができる。
  • 再現の難しい(乱数が絡んだり)テストにて、固定値化することで、期待値と 一致するかどうかを確かめやすくする。

事前準備

  • pom.xmlにdependencyを追加する。
pom.xml
    <dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
    </dependency>
  • MockのクラスとMock注入先クラスを用意する。
DBConnecter.java
// モッククラス
public class DBConnecter {
    public String getConnecter() {
        return "_Connection_";
    }
}
SampleDAO.java
public class SampleDAO {
    // モック注入先クラス
    private DBConnecter connecter;
    public select() {
        System.out.println("接続情報「" + connecter.getConnecter() + "」を使用してSELECTします。");
    }
}

モックテストクラス

①モック注入をしない場合

  • 実行結果はsampleDAO#connecterは当然nullなのでgetConnecter()呼出時に  NullPointer例外になる。
SampleDAOTest.java
public class SampleDAOTest
{
    DBConnecter connecter = new DBConnecter();
    SampleDAO sampleDAO = new SampleDAO();
    @Before
    public void init() {
    }
    @Test
    public void testcase001() {
        sampleDAO.select();
        assertEquals(true, true);
    }
}

②-1モック注入をする。(@Mock, @InjectMock, initMock())

  • @Mockをモッククラスに付与  DBConnecter connecter = mock(DBConnecter.class);でも同様の挙動を見せる。
  • @InjectMockをモック注入先クラスに付与
  • MockitoAnnotations.initMocks(this); でアノテーションを有効化する。これがないと①と同様にNullPointer例外のままになる。
SampleDAOTest.java
public class SampleDAOTest
{
    @Mock
    DBConnecter connecter;
    @InjectMocks
    SampleDAO sampleDAO = new SampleDAO();
    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void testcase001() {:thinking:
        sampleDAO.select();
        assertEquals(true, true);
    }
}

【出力結果】
接続情報「null」を使用してSELECTします。

:thinking: あれ?connecterのインスタンスは作成されたのに、「null」なのはなぜに?

②-2 デバックで覗いてみた。

  • DBConnecter#getConnecter()にブレークポイントを置いてみた。 ⇒ ここで停止できませんというエラーが発生した。
  • sampleDAO にカーソルを合わせてSampleDAO#connecterの型を確認してみた。 ⇒ 謎な型 : DBConnecter#EnhancerByMockito~~~
  • getConnection()の定義にSystem.out.println("XXX")を置いてみた。⇒ 呼ばれない。

上記3点から@Mockによるインスタンスはnewのインスタンスと明らかに異なる。
そのため「モック」(実物に似た模型)と呼ばれているのだなぁ

(厳密には空とは少し違うかもだが感覚をつかむ分にはいいでしょう。)

③-1 - モックにテスト用戻り値を設定する。(引数なし) when().thenReturn(), doReturn().when

  • when(mockInstance#mockMethod()).thenReturn("want to set value"); 特定のモックインスタンスが呼ばれたとき(when)、指定した値を返却(return)する。

【出力結果】
接続情報「override result by when()」を使用してSELECTします。

SampleDAOTest#testcase001()
        when(connecter.getConnecter()).thenReturn("override result by when()");
        sampleDAO.select();
        assertEquals(true, true);

③-2 - モックにテスト用戻り値を設定する。(引数が固定値) when().thenReturn()

  • mockMethod()が引数ありの場合、引数に応じて設定値を変えられる。
DBConnecter.java
// モッククラス
public class DBConnecter {
    public String getConnecter(int i, boolean b) {
        return "_Connection_";
    }
}
SampleDAO.java
public class SampleDAO {
    // モック注入先クラス
    private DBConnecter connecter;
    public select() {
        System.out.println("接続情報「" + connecter.getConnecter(1, true) + "」を使用してSELECTします。");
        System.out.println("接続情報「" + connecter.getConnecter(2, false) + "」を使用してSELECTします。");
        System.out.println("接続情報「" + connecter.getConnecter(-1, false) + "」を使用してSELECTします。");

    }
}
SampleDAOTest#testcase001()
        when(connecter.getConnecter(1, true)).thenReturn("getConnection() args 1, true");
        when(connecter.getConnecter(2, false)).thenReturn("getConnection() args 2, false");
        sampleDAO.select();
        assertEquals(true, true);

【出力結果】
接続情報「getConnection() args 1, true」を使用してSELECTします。
接続情報「getConnection() args 2, false」を使用してSELECTします。
接続情報「null」を使用してSELECTします。

③-3 - モックにテスト用戻り値を設定する。(引数が任意値) when().thenReturn()

  • ③-2の場合、引数は100通りあるけど戻り値は1通りなんだけどという場合when()を100行書かないといけない。
  • そういう引数は何でもいいんだけどという場合はMockito.Matchers.anyXXX()を使用する。
SampleDAOTest#testcase001()
        when(connecter.getConnecter(anyInt(), anyBoolean())).thenReturn("getConnection() args any, any");
        sampleDAO.select();
        assertEquals(true, true);

【出力結果】
接続情報「getConnection() args any, any」を使用してSELECTします。
接続情報「getConnection() args any, any」を使用してSELECTします。
接続情報「getConnection() args any, any」を使用してSELECTします。

④-1 モックに例外を設定する。 doThrow().when()

  • 戻り値がvoid型の場合、when().thenReturn()は使えない。
  • ③では戻り値を設定していたが、④-1では例外を設定することができる。
  • 本来の使い方としてはthrowsで投げられる可能性がある例外を設定すべき (doThrowにはNullPointerExceptionを設定すべきということ。)
Sample1.java
public class Sample1{
    public void voidMethod() throws NullPointerException {
        throw new NullPointerException();
    }
}
SampleDAOTest#testcase001()
        doThrow(new IllegalArgumentException()).when(sample1).voidMethod();
        try {
            sample1.voidMethod();
        } catch (NullPointerException e) {
            System.out.println("NullPointerExceptionが発生しました。");
        } catch (IllegalArgumentException e) {
            System.out.println("IllegalArgumentExceptionが発生しました。");
        }
    }

【出力結果】
IllegalArgumentExceptionが発生しました。

⑤ モックの一部のみテスト用戻り値を設定する。@Spy

  • @Mockはすべてのメソッドがモック化するが、@Spyはwhen()で設定したメソッドのみ モック化する。
  • 状況によって@Mock@Spyを使い分けることになりそう。
Sample1.java
public class Sample1{
    public String method1() {
        return "called method1";
    }
    public String method2() {
        return "called method2";
    }
}
SampleDAOTest.java
public class SampleDAOTest
{
  @Spy
  Sample1 sample1 = new Sample1();
    @Before
    public void init() {
    }
    @Test
    public void testcase001() {
        when(sample1.method1()).thenReturn("when called method1");
        System.out.println(sample1.method1());
        System.out.println(sample1.method2());

【出力結果】
when called method1
called method2

⑥ Verifyを用いたテスト

  • verifyは@mock, @spyいずれのインスタンスに対しても使用可能

⑥-1 メソッドが指定回数呼ばれたか判定 times

  • verify(spy or mock, times(want)).method(args)でtime(want)には メソッドの実行回数の期待値を設定する。
  • 実際の回数が期待値が異なる場合例外が発生しテストケースが失敗する。
  • argsにはwhen()と同様にanyXXX()による指定ができる。
Sample1.java
public class Sample1{
    public String method1(int i) {
        return "i";
    }
}
SampleDAOTest#testcase001()
        sample1.method1(10);
        sample1.method1(20);
        sample1.method1(20);    
        verify(sample1,times(2)).method1(20);         // 1回目 
        verify(sample1,times(2)).method1(anyInt());   // 2回目
        verify(sample1,times(3)).method1(20);         // 3回目

【出力結果】
3回目のverify()で例外が発生。
 TooLittleActualInvocations() sample1.method1(20);
 Wanted 3 times: But was 2 times

⑥-2 メソッドが指定回数呼ばれたか判定 atLeastOnce, atLeast, atMost, never

  • verifyの引数にはtimes()以外にも渡すことができる。
  • 実際の回数が期待値が異なる場合例外が発生しテストケースが失敗する。
SampleDAOTest#testcase001()
        verify(sample1,atLeastOnce()).method1(0); // 最後に呼ばれたメソッドと引数が一致で正常
        verify(sample1, atLeast(2)).method1(20);   // 2回「以上」で正常
        verify(sample1, atMost(2)).method1(20);    // 2回「以下」で正常
        verify(sample1,never()).method1(20);       // 0回で正常 (times(0))と等価

【出力結果】

回数 例外メッセージ
atLeastOnce Argument(s) are different!
atLeast TooLittleActualInvocations
atMost MockitoAssersionError
never NeverWantedButInvoked

⑦ private変数へのInjectをする。 Whitebox.setInternalState()

  • やることは@MockInjects,@mock,@spyと等価。(厳密には違うかもだが)
  • 第二引数が文字列になっておりミスタイプが起こる可能性があるため setInternalState()は使わず、@MockInjects,@mock,@spyを使うのがよい。
SampleDAOTest.java
public class SampleDAOTest {
    Sample1 sample1 = new Sample1();
    Sample2 sample2 = new Sample2();
    @Before
    public void init() {
        Whitebox.setInternalState(sample2, "sample1", sample1);
    }
    @Test
    public void testcase001() {
        System.out.println("2倍した結果は「" + sample2.twice(10) + "」です。");
    }
}

完走した感想

  • 初めてこのサイトで記事を書いたが、やはりソースコードのフォーマッタが簡単にかけられるのが良い。
  • 無計画に記事を書いたので関連すべき情報がとっ散らかってしまった。 (@Mock@Spyとかが近くに掛かれていなかったり)

残課題

・公式のガイドを一通り目を通したい。
・doXXX()のその他の使いかた
.when().thenReturn()とdoXXX().when()の違い。
・PowerMockとは?

79
87
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
87