エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

Mockito+Springで単体テストを書く

業務ではバックエンドにSpringを使っているのですが、単体テストを書く場合のモックフレームワークとしてMockitoを使っています。

割合としてはE2Eテストの方が多く単体テストを書く機会が少ないのですが、今後は単体テストも割合を増やしていきましょうということで最近書くことが増えてきました。

というわけで今回はMockitoについて調べたことを残しておこうと思います。

build.gradle

何はともあれorg.mockito:mockito-coreをdependenciesに追加してMockitoを使えるようにしてあげます。

dependencies {
    testImplementation "org.mockito:mockito-core:3.+"
        ...
}
...

クラスをモックする

Mockitoではクラスをモックする際に複数の書き方があります。

@Mockでモックする

@RunWith(MockitoJUnitRunner.class)
public class TestClass {
    @Mock
    private HogeService hogeService;
    ...
}

@Mockを使う場合別途初期化しないと使えません。

初期化する方法は以下の3種類。

  • @BeforeEachinitMocks(this)を呼ぶ
  • @RunWith(MockitoJUnitRunner.class)をテスト用のクラスに付与する
  • @Rule public MockitoRule rule = MockitoJUnit.rule();をフィールドに追加する

@MockBeanでモックする

@RunWith(MockitoJUnitRunner.class)
public class TestClass {
  @MockBean
  private HogeService hogeService;
  ...
}

こちらはSpring Bootが提供するアノテーションで、作成されたモックがApllication Contextへ登録されるようになります。 @MockBeanで指定したクラスはSpringのDIの機構によって注入される為、例えば他のクラスでHogeServiceを@Autowiredしていれば、モックされたHogeServiceが注入されるようになります。

こちらもモックを動作させるために@RunWith(MockitoJUnitRunner.class)で初期化が必要になります。

mock()でモックする

public class TestClass {
  private HogeService hogeService;

  @BeforeEach
  public void beforeEach() {
    hogeService = mock(hogeService);
  }
  ...
}

アノテーションではなくメソッドでモックする方法です。 モックが必要になったタイミングでmock()を呼んであげます。

モック対象が増えてくるとちょっと行数がかさみそうです。

モックしたクラスをテスト対象クラスにInjectする

@InjectMocksを付与すると付与したクラスに@MockでモックしたクラスをInjectできます。

@RunWith(MockitoJUnitRunner.class)
public class TestClass {
    @Mock
    private HogeService hogeService;
    ...
    @InjectMocks
    private TargetService sut;
}

public class TargetService {
    private HogeService hogeService;
    ...
}

↑の例ではTargetServiceに@InjectMocksを付与することで、TargetServiceのhogeServiceにモックをInjectしています。

モック化したクラスのメソッドの挙動を定義する

テストの際、モックの挙動をテストコード側で制御したい時がよくあります。そんな時に使うメソッド群です。

doReturn().when()

モック化したクラスのメソッドが叩かれた際の戻り値を定義します。

例えばlongの戻り値を持つHogeServiceクラスのgetLongメソッドの挙動を定義したい場合、以下のように書きます。

@Mock
private HogeService hogeService;
...
@Test
public void test1() {
  // hogeService.getLong()は常に1をreturnするようになる
  doReturn(1).when(hogeService).getLong();
  ...
}

もし特定の型の引数を必要とするメソッドの挙動を定義したい場合は、以下のようになります。

@Mock
private HogeService hogeService;
...
@Test
public void test1() {
  // hogeService.getUser(Long userId, String username)は常に決められたUserインスタンスをreturnするようになる
  User tanaka = new User(1, "田中 太郎");
  doReturn(tanaka).when(hogeService).getUser(anyLong(), anyString());
  ...
}

MockitoではanyLong()やanyString()といったargument matcherを利用することで、特定の値を指定せず型を指定して挙動を定義できます。

getUser(anyLong(), anyString())は、第1引数がLong, 第2引数がStringならばtanakaを常にreturnすることを定義しています。

cf. argument matcherについて Mockito - mockito-core 5.1.1 javadoc

doNothing().when()

モック化したクラスのメソッドが叩かれた際、何もしないことを定義します。

@Mock
private HogeService hogeService;
...
@Test
public void test1() {
  // hogeService.connectDB()は呼ばれても何もしない
  doNothing().when(hogeService).connectDB();
  ...
}