【Java】Mockitoでモック化したメソッドを複数回呼ぶ方法
Mockitoでモック化したメソッドを複数回呼ぶ方法です。
実行環境は以下になります。
JDK:17 Junit:4.13.2 Mockito:4.0.0
引数が基本型の場合
以下のような基本型の引数のケースです。
StringやLocalDateなどの参照型でも基本型に扱いが近しいものはこのケースに含まれます。
public String execute1(int no) { return null; // do something }
doReturnを複数定義し、引数に想定の値をセットすればOKです。
doReturn("1st").when(logic).execute1(1); doReturn("2nd").when(logic).execute1(2);
引数が参照型の場合(フィールドが基本型のみ)
以下のような参照型の引数のケースです。
なおかつ、参照型クラスのフィールドが基本型のみのケースです。
public String execute2(Dto dto) { return null; // do something }
doReturnを複数定義し、引数に想定の値をrefEq()でセットすればOKです。
Dto dto1 = new Dto(); dto1.setNo(1); Dto dto2 = new Dto(); dto2.setNo(2); doReturn("1st").when(logic).execute2(refEq(dto1)); doReturn("2nd").when(logic).execute2(refEq(dto2));
引数が参照型の場合(フィールドに参照型が含まれる)
以下のような参照型の引数のケースです。
なおかつ、参照型クラスのフィールドに別の参照型クラスが含まれるケースです。
public String execute3(NestDto nestDto) { return null; // do something }
ArgumentMatcherは一つのメソッドに対して複数定義することができません。
そこで、doReturn()のメソッドチェーンとArgumentCaptorを使って対応します。
doReturn()はメソッドチェーンで繋げることができるので、1回目に返す値、2回目に返す値を指定することができます。
メソッドの引数にany()を使えば、どんな引数が来てもdoReturn()が適用されるので、これで複数回呼びことが可能になります。
any()を使うと引数の検証ができないので、ArgumentCaptorを使います。
ArgumentCaptorは実際にメソッドに渡された引数をキャプチャしてくれるので、ArgumentCaptorから引数の値を取得して検証します。
doReturn("1st").doReturn("2nd").when(logic).execute3(any(NestDto.class)); ArgumentCaptor<NestDto> captor = ArgumentCaptor.forClass(NestDto.class); List<String> actual = target.execute3(logic); verify(logic, times(2)).execute3(captor.capture()); assertThat(captor.getAllValues().get(0).getDto().getNo(), is(1)); assertThat(captor.getAllValues().get(1).getDto().getNo(), is(2));
ソースコード
import lombok.Getter; import lombok.Setter; @Getter @Setter public class Dto { private int no; }
import lombok.Getter; import lombok.Setter; @Getter @Setter public class NestDto { private Dto dto; }
public class Logic { // 引数が基本型の場合 public String execute1(int no) { return null; // do something } // 引数が参照型の場合(フィールドが基本型のみ) public String execute2(Dto dto) { return null; // do something } // 引数が参照型の場合(フィールドに参照型が含まれる) public String execute3(NestDto nestDto) { return null; // do something } }
import java.util.Arrays; import java.util.List; public class Sample { // 引数が基本型の場合 public List<String> execute1(Logic logic) { String str1 = logic.execute1(1); String str2 = logic.execute1(2); return Arrays.asList(str1, str2); } // 引数が参照型の場合(フィールドが基本型のみ) public List<String> execute2(Logic logic) { Dto dto1 = new Dto(); dto1.setNo(1); String str1 = logic.execute2(dto1); Dto dto2 = new Dto(); dto2.setNo(2); String str2 = logic.execute2(dto2); return Arrays.asList(str1, str2); } // 引数が参照型の場合(フィールドに参照型が含まれる) public List<String> execute3(Logic logic) { NestDto dto1 = new NestDto(); dto1.setDto(new Dto()); dto1.getDto().setNo(1); String str1 = logic.execute3(dto1); NestDto dto2 = new NestDto(); dto2.setDto(new Dto()); dto2.getDto().setNo(2); String str2 = logic.execute3(dto2); return Arrays.asList(str1, str2); } }
import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.*; public class SampleTest { // 引数が基本型の場合 @Test public void test1() { // セットアップ Sample target = new Sample(); Logic logic = mock(Logic.class); doReturn("1st").when(logic).execute1(1); doReturn("2nd").when(logic).execute1(2); // テスト実行 List<String> actual = target.execute1(logic); // 結果検証 verify(logic, times(1)).execute1(1); verify(logic, times(1)).execute1(2); assertThat(actual.get(0), is("1st")); assertThat(actual.get(1), is("2nd")); } // 引数が参照型の場合(フィールドが基本型のみ) @Test public void test2() { // セットアップ Sample target = new Sample(); Logic logic = mock(Logic.class); Dto dto1 = new Dto(); dto1.setNo(1); Dto dto2 = new Dto(); dto2.setNo(2); doReturn("1st").when(logic).execute2(refEq(dto1)); doReturn("2nd").when(logic).execute2(refEq(dto2)); // テスト実行 List<String> actual = target.execute2(logic); // 結果検証 verify(logic, times(1)).execute2(refEq(dto1)); verify(logic, times(1)).execute2(refEq(dto2)); assertThat(actual.get(0), is("1st")); assertThat(actual.get(1), is("2nd")); } // 引数が参照型の場合(フィールドに参照型が含まれる) @Test public void test3() { // セットアップ Sample target = new Sample(); Logic logic = mock(Logic.class); doReturn("1st").doReturn("2nd").when(logic).execute3(any(NestDto.class)); ArgumentCaptor<NestDto> captor = ArgumentCaptor.forClass(NestDto.class); // テスト実行 List<String> actual = target.execute3(logic); // 結果検証 verify(logic, times(2)).execute3(captor.capture()); assertThat(captor.getAllValues().get(0).getDto().getNo(), is(1)); assertThat(captor.getAllValues().get(1).getDto().getNo(), is(2)); assertThat(actual.get(0), is("1st")); assertThat(actual.get(1), is("2nd")); } // 引数が参照型の場合(フィールドに参照型が含まれる) // ArgumentMatcherを使った失敗例 @Test public void test4() { // セットアップ Sample target = new Sample(); Logic logic = mock(Logic.class); ArgumentMatcher<NestDto> matcher1 = argument -> { assertThat(argument.getDto().getNo(), is(1)); return true; }; ArgumentMatcher<NestDto> matcher2 = argument -> { assertThat(argument.getDto().getNo(), is(2)); return true; }; doReturn("1st").when(logic).execute3(argThat(matcher1)); doReturn("2nd").when(logic).execute3(argThat(matcher2)); // matcher1が上書きされる // テスト実行 List<String> actual = target.execute3(logic); // 結果検証 verify(logic, times(1)).execute3(argThat(matcher1)); // NG verify(logic, times(1)).execute3(argThat(matcher2)); assertThat(actual.get(0), is("1st")); assertThat(actual.get(1), is("2nd")); } }
関連記事
Mockitoの引数の検証方法については以下を参考にして下さい。