LoginSignup
1
0

More than 1 year has passed since last update.

riverpodとflutter_hooksで作った無限スクロール画面のテストを書く

Posted at

前回の記事の続きです

ページングの画面のwidgetテストをmockitoを使って書いてみます

事前準備

テスタブルなコードに改善します。前回の実装ではViewModelでリポジトリのインスタンスを生成していました。mock化できるようにリポジトリをprovider経由で取得するように変更します

user_repository.dart
final userRepositoryProvider = Provider((ref) => UserRepository());

class UserRepository {

}

ViewModel内では、provider経由でリポジトリのインスタンスを取得します。

users_view_model.dart


final usersViewModelProvider =
    StateNotifierProvider<UsersViewModel, UsersViewState>(
  (ref) {
    return UsersViewModel(
      // ここでrefを渡す
      ref, 
      UsersViewState.initial(),
    );
  },
);

class UsersViewModel extends StateNotifier<UsersViewState> {
  UsersViewModel(
    this._ref, [
    UsersViewState? usersViewState,
  ]) : super(usersViewState ?? UsersViewState.initial());

  final Ref _ref;

  // provider経由でリポジトリを取得する
  UserRepository get _userRepository => _ref.read(userRepositoryProvider);

  Future<void> fetchPage(
    int page,
    void Function(UsersResponse) onSuccess,
    void Function(String) onError,
  ) async {
    try {
      final response = await _userRepository.getUsers(page);
      onSuccess(response);
    } catch (e) {
      onError('error');
    }
  }
}


テストの内容

リスト画面では、手動テストであっても表示内容と追加読み込みを確認すると思います。今回も以下の内容をテストしてみます。

  1. 全1ページの場合
    1. 1つ目の要素が描画されるか
    2. 最後までスクロールして最後の要素が描画されるか
  2. 複数ページの場合
    1. 2ページ目のデータの最後の要素が描画されるか

リポジトリのmock化

mockitoでmock用クラスを生成し、メソッドの返却値を変更します。

users_view_test.dart

import 'users_view_test.mocks.dart';

@GenerateNiceMocks([
  MockSpec<UserRepository>(),
])
void main() {
  testWidgets('1st page load', (widgetTester) async {
    final mockUserRepository = MockUserRepository();
// 続く

1ページのみの場合

users_view_test.dart
    // 1
    when(mockUserRepository.getUsers(0)).thenAnswer(
      (_) async {
        return UsersResponse(
          page: 1,
          isLast: true,
          users: List.generate(
            20,
            (index) => User(
              id: index,
              name: 'test-name-$index',
            ),
          ),
        );
      },
    );
    // 2
    await widgetTester.pumpWidget(
      ProviderScope(
        overrides: [
          userRepositoryProvider.overrideWithValue(mockUserRepository)
        ],
        child: MaterialApp(
          home: Material(
            child: UsersView(),
          ),
        ),
      ),
    );
    await widgetTester.pumpAndSettle();

    // 3
    expect(find.text('test-name-0'), findsOneWidget);

    // 4
    await widgetTester.drag(
      find.byType(PagedListView<int, User>),
      const Offset(0.0, -2000),
    );
    await widgetTester.pumpAndSettle();
    // 5
    expect(find.text('test-name-19'), findsOneWidget);
  });

コメントで番号を振っているので順に解説します。

  1. 1ページ目のAPIリクエストの場合にisLast=trueのデータを返却するようにmock化する
  2. ProviderScopeを利用して、userRepositoryProviderがmockリポジトリを返却するようにoverrideする。
  3. 生成後の描画処理を待った後に、最初の要素が表示されていることを確認する
  4. リストビューを十分にドラッグする
  5. 画面外にあった要素が描画されるのを待った後に、最後の要素が表示されていることを確認する。

ポイントは以下の2点です

  • ウィジェットテストで利用される画面は小さい。デフォルトは800×600らしいです
  • 画面外の要素は描画されない。最初これがわからず、ListTileの数をチェックしたりしていました。

2ページ目

1ページ目とさほど変わりませんが、1ページ目のドラッグ後に2ページ目用のフェッチを見越して再度ドラッグetcをしています

    when(mockUserRepository.getUsers(0)).thenAnswer(
      (_) async {
        return UsersResponse(
          page: 0,
          isLast: false,
          users: List.generate(
            20,
            (index) => User(
              id: index,
              name: 'test-name-$index',
            ),
          ),
        );
      },
    );
    when(mockUserRepository.getUsers(1)).thenAnswer(
      (_) async {
        return UsersResponse(
          page: 1,
          isLast: true,
          users: List.generate(
            5,
            (index) => User(
              id: index + 20,
              name: 'test-name-${index + 20}',
            ),
          ),
        );
      },
    );

    await widgetTester.pumpWidget(
      ProviderScope(
        overrides: [
          userRepositoryProvider.overrideWithValue(mockUserRepository)
        ],
        child: MaterialApp(
          home: Material(
            child: UsersView(),
          ),
        ),
      ),
    );
    await widgetTester.pumpAndSettle();

    expect(find.byType(ListTile), findsWidgets);
    expect(find.text('test-name-0'), findsOneWidget);

    await widgetTester.drag(
      find.byType(PagedListView<int, User>),
      const Offset(0.0, -2000),
    );
    await widgetTester.pumpAndSettle();
    expect(find.text('test-name-19'), findsOneWidget);
    // 2ページ目のデータが読み込まれたはずなので再度ドラッグする
    await widgetTester.drag(
      find.byType(PagedListView<int, User>),
      const Offset(0.0, -2000),
    );

    await widgetTester.pumpAndSettle();
    expect(find.text('test-name-24'), findsOneWidget);

まとめ

ページングの画面のテストを書きました。mockitoでレスポンスをmock化し、想定通りのレスポンスがAPIから返却されれば正しい振る舞いになることを確認できました。
確認が難しいエラー画面もエラー用のwidgetを作成して正しく定義すれば簡単にテストできます。
僕の経験上、一覧画面系の手動テストはデータ依存で雑になりがちなので、これを機会に自動化してみるのはいかがでしょうか

参考リンク

1
0
0

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
1
0