FlutterでWidget testをする。(with Provider)


## いるべです。 Flutterやってます。

### Flutter

UI構築がとても簡単でいいですよね。ウンウン
この記事を読んでいる方は少なからずFlutterに関心があるんでしょう。ウンウン

今回はProvider使用時のWidget testについて簡単に書いていきます。
(え？Riv○rP○d？ちょっとよく分からないなぁ。)

## 画面数が増えるとテストがめんどい。

swiftで一画面単体のテストってあったんでしょうか？ ((真上にめんどいって書いたくせにな

FlutterではWidget testを利用して作った画面単体でテストすることができます。

これで画面数が増えても気軽にテストすることができますね！

### Widget test

state notifierを使っています。
freezed使った方がいいですがこれだけなので正直めんどくさかった。copyWith便利。

```dart:lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:state_notifier/state_notifier.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StateNotifierProvider<ViewModel, State>(
        create: (context) => ViewModel(),
        child: Home(),
      ),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(context.watch<State>().one.toString()),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<ViewModel>().toggle(),
      ),
    );
  }
}

class ViewModel extends StateNotifier<State> {
  ViewModel(): super(State(false));

  void toggle() {
    state = State(!state.one);
  }
}

class State {
  State(this.one);

  bool one;
}
```

都合上、１ファイルに記述。
コードの命名などは二の次٩( ᐛ )و

大事なのは「stateを参照して、そのstateが持っているboolを、FloatingActionButtonをタップするたびにトグルするアプリ」であるということ

<img width=250 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601990/302bf3cb-fa34-05c9-0168-7bf7d59ea377.jpeg">

これのテストコードが下記

```dart:test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:state_notifier/state_notifier.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:disposable/main.dart' as home;

void main() {
  testWidgets('Float Button Test', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: StateNotifierProvider<home.ViewModel, home.State>(
        create: (context) => TestViewModel1(),
        child: home.Home(),
      ),
    ));

    final floatbtn = find.byWidgetPredicate((widget) => widget is FloatingActionButton);
    expect(floatbtn, findsOneWidget);

    expect(find.text('true'), findsOneWidget);
    await tester.tap(floatbtn);
    await tester.pump();
    expect(find.text('false'), findsOneWidget);

  });
}

class TestViewModel1 extends StateNotifier<home.State> implements home.ViewModel {
  TestViewModel1(): super(home.State(true));

  @override
  void toggle() {
    state = home.State(!state.one);
  }

}

class TestViewModel2 extends home.ViewModel {

  @override
  void toggle() {
    state = home.State(!state.one);
  }
}
```

TestViewModelは２種類の形を取れる。試してないだけで他もやり方あるはず。

注意点は、

- testディレクトリからlibディレクトリのクラスを参照したい、使いまわしたいときはimportしたものをas homeなどで名前をつけてそこから参照する必要がある。
- Providerで利用するために指定する型は元のクラスを参照する必要がある。```StateNotifierProvider<TestViewModel, State>(create: ...);``` ではFloatingActionButtonでViewModelを参照しておりTestViewModelを取得できないのでエラーになる。
- モックとして置き換えるクラスは、元になるクラスを継承しておく必要がある。(ここ要調査かも？)

```tester.pump()``` で画面を更新してくれる。これを入れなければ画面が更新されないため、falseを期待しているテストコードでこける。ここちょっとつまづいた。

#### (RiverPodでもやりたい)

## ご愛読ありがとうございました。いるべ先生の次回作にご期待ください。