🧪

モックとスタブについて

2024/07/01に公開

テストについてお勉強してると見るワード

最近テストコードの書き方について本を読んで勉強しました📚
本を読むとモックとかスタブと出てきます?

何だそれ?

書籍からの引用によると

モックはテスト対象システムからその依存に向かって行われる 外部に向かうコミュニケーション(出力)を模倣し、 そして、検証するのに使われる。このとき、モックが模倣するコミュニケーションはテスト対象システムが依存の状態を変えるために行うその依存 への呼び出しのことになる。
スタブは依存からテスト対象システムに向かって行われる内部に向かうコミュニケーション(入力)を模倣するのに使われる。 このとき、スタブが模倣するコミュニケーションはテスト対象システムが依存からデータを取得するために行う その依存への呼び出し のことになる。

わかりにくいな😅
参考なりそうな例を以下に記載します。

モック(Mock)

モックは、テスト対象のコードが期待通りに動作していることを確認するために使用されます。モックは、テスト対象のコードが依存しているオブジェクトの動作を模倣しますが、その動作はテストの中で明示的に定義されます。モックは、特定のメソッドがどのように呼び出されるか(どの引数で、何回、どの順序で等)を検証するために使用されます。 例えば、外部APIへのリクエストを行うメソッドをテストする場合、そのメソッドがAPIに対して正しいリクエストを行っていることを確認するためにモックを使用します。

スタブ(Stub)

スタブは、テスト対象のコードが依存しているオブジェクトの一部の動作を模倣します。しかし、スタブはモックとは異なり、その動作は通常、テストの前に設定され、テスト中には変更されません。スタブは、テスト対象のコードが期待通りの結果を生成するために必要な特定の値を提供するために使用されます。 例えば、データベースからのデータ取得を行うメソッドをテストする場合、そのメソッドがデータベースから期待通りのデータを取得できることを確認するためにスタブを使用します。

try test!

モックでテストやってみましょう!
mockitoなるものを使う必要がある。こちらはコードの自動生成が必要なので、build_runnerも追加する。

https://pub.dev/packages/mockito
https://pub.dev/packages/build_runner

test/ディレクトリ配下に、テストコードを作成する。ファイル名の付け方に決まりがあるみたい。
timer_service_test.mocks.mocks.dartというファイルを作成。こちらにモックのコードを定義したら、自動生成のコマンドを実行する。成功すると、テストに必要なメソッドが使えるようになる。

モックタイマーのテスト

タイマーのロジックを使ったテストコードを今回作成しました。
mockitoを使ってモックタイマーを作成し、その動作をテストしています。

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'timer_service_test.mocks.mocks.dart';

([TimerService])
class TimerService {
  Future<String> delayedResponse(Duration delay) {
    return Future.delayed(delay, () => 'Delayed response');
  }
}

void main() {
  test('Mock timer test', () async {
    var mockTimerService = MockTimerService();

    // メソッドが呼び出されたときにすぐに'Stub'を返すFutureを設定します。
    when(mockTimerService.delayedResponse(any))
        // Future.value()は、指定された値を返すFutureを作成します。
        .thenAnswer((_) => Future.value('Stub'));

    // メソッドが期待する値を返すことを確認します。
    expect(await mockTimerService.delayedResponse(const Duration(seconds: 5)),
        'Stub');
  });
}

このテストコードは、TimerServiceクラスのdelayedResponseメソッドのモックを作成し、
その動作をテストしています。 まず、TimerServiceクラスを定義しています。このクラスには、
指定された遅延後に文字列を返すFutureを返すdelayedResponseメソッドがあります。
次に、main関数内でテストを定義しています。テストの名前は'Mock timer test'です。
テストの中で、まずTimerServiceクラスのモックを作成しています。このモックはmockTimerService変数に格納されます。
次に、when関数を使用してdelayedResponseメソッドが呼び出されたときの戻り値を設定しています。この例では、メソッドが
呼び出されたときにすぐに'Stub'を返すFutureを返します。 最後に、expect関数を使用して、delayedResponseメソッドが
期待する値を返すことを確認しています。この例では、メソッドが'Stub'を返すことを期待しています。 Future.valueについては、
これは指定された値を持つ新しいFutureを作成します。このFutureはすぐに完了し、その値を返します。この機能は、非同期操作がすぐ
に結果を返すことが既知である場合や、非同期操作の結果を模擬するためのテストでよく使用されます。

自動生成のコマンド:

flutter pub run build_runner watch --delete-conflicting-outputs

テストを実行する。結果は成功している。

失敗する例だと、'Wrong value'を返すFutureを設定すると、メソッドが期待する値('Stub')を返すと思いきや違うので、エラーが出ます!

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'timer_service_test.mocks.mocks.dart';

([TimerService])
class TimerService {
  Future<String> delayedResponse(Duration delay) {
    return Future.delayed(delay, () => 'Delayed response');
  }
}

void main() {
  test('Mock timer test', () async {
    var mockTimerService = MockTimerService();

    // メソッドが呼び出されたときにすぐに'Wrong value'を返すFutureを設定します。
    when(mockTimerService.delayedResponse(any))
        .thenAnswer((_) => Future.value('Wrong value'));

    // メソッドが期待する値('Stub')を返すことを確認します。
    // しかし、実際には'Wrong value'が返されるため、このテストは失敗します。
    expect(await mockTimerService.delayedResponse(const Duration(seconds: 5)),
        'Stub');
  });
}

まとめ

モックとスタブの主な違いは、モックがテスト対象のコードの動作を検証するために使用されるのに対し、スタブはテスト対象のコードが期待通りの結果を生成するために必要な値を提供するために使用されるという点です

Discussion