January 31, 2022

DartのテストライブラリMockitoで複数回モックする

DartにはMockito というモック用のテストライブラリがあります。 Javaにも同じ名前のライブラリ があってDartの方はこのライブラリをインスパイアして作られているため、元々JavaでMockitoを使っていた方はすぐに使えるようになると思います。

DartのMockitoも使い方は大体同じなのですが、同じ引数のメソッドを複数回モックするときに困ったのでメモします。

Repositoryクラスをコンストラクタで渡して動くServiceクラスを用意します。
中身はシンプルに掛け算をして足しているだけです。

  • ExampleRepository
class ExampleRepository {
  int multiplication(int argument) {
    return argument * 100;
  }
}
  • ExampleService
class ExampleService {
  ExampleService(this.exampleRepository);

  final ExampleRepository exampleRepository;

  int calculate(int a, int b) {
    return exampleRepository.multiplication(a) +
        exampleRepository.multiplication(b);
  }
}

Mockitoを使ったテスト

本家のJavaではthenReturnの引数が複数回とれるようになっているので、1回目の呼び出しと2回目の呼び出しが異なる場合に順番どおりにmockできます。

  • Java
@ExtendWith(MockitoExtension.class)
public class ExampleServiceTest {

    @Mock
    ExampleRepository mExampleRepository;

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

    @Test
    public void testSaveArgMatch() throws Exception {

        final ExampleService exampleService = new ExampleService(mExampleRepository);

        // 1回目:1000, 2回目:2000
        Mockito.when(mExampleRepository.multiplication(10)).thenReturn(1000, 2000);

        final int result = exampleService.calculate(10, 10);

        assertEquals(3000, result);
    }
}

この場合 mExampleRepository.multiplication(10) の1回目の結果は1000になって、2回目の結果は2000にモックされます。

  • Dart

JavaのようにDartでも thenReturnを複数セットしたいのですが現状は書けません。

class MockExampleRepository extends Mock implements ExampleRepository {}

void main() {
  final exampleRepository = MockExampleRepository();
  final service = ExampleService(exampleRepository);

  setUp(() {
    reset(exampleRepository);
  });

  test('test', () async {
    when(exampleRepository.multiplication(10)).thenReturn(1000, 2000); // syntax error

    final result = service.calculate(10, 10);

    expect(result, 3000);
  });
}

Dartでも同じメソッドで異なる値を複数回モックするには、intのcall countのような変数を用意して配列でインクリメントします。

var callCount = 0;
when(mockedFoo.bar()).thenAnswer((_) => [1000, 2000][callCount++]);

ポイントはthenReturnではなくthenAnswerを使うことです。
今回モックするメソッドはFutureではないので単純にモックするだけならthenReturnでも問題ないのですが、このように複数回モックする場合はthenAnswerで呼ぶ必要があります。

Dart版の完成形はこのようになります。

class MockExampleRepository extends Mock implements ExampleRepository {}

void main() {
  final exampleRepository = MockExampleRepository();
  final service = ExampleService(exampleRepository);

  setUp(() {
    reset(exampleRepository);
  });

  test('test', () async {
    var callCount = 0;

    when(exampleRepository.multiplication(10)).thenAnswer((_) => [1000, 2000][callCount++]);
    
    final result = service.calculate(10, 10);

    expect(result, 3000);
  });
}

これでDartのMockitoでも複数回呼び出しで異なる値を返せるようになりました。
Mockitoのドキュメントにはなかったので(多分)メモでした。

© AAkira 2023