- こちらのチュートリアル(11)と(12)の解説をします。
- チュートリアルを読むのが早いと思いますが、概要をまとめたのでこちらもご参照ください。
- Blocや他のPresentation logic holderは、Domain層とPresentation層の間に位置する
UseCase -> Presentation Logic Holders -> Widgets
Event-Drivenテスト
-
mapEventToState()
にてロジックが実行される
Streamに適したテスト方法
- Blocは
Stream<Event>
なのでawait untilCalled()
を用いてテストする
test.dart
test(
'should call the InputConverter to validate and convert the string to an unsigned integer',
() async {
// arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Right(tNumberParsed));
// act
bloc.dispatch(GetTriviaForConcreteNumber(tNumberString));
await untilCalled(mockInputConverter.stringToUnsignedInteger(any));
// assert
verify(mockInputConverter.stringToUnsignedInteger(tNumberString));
},
);
-
expectLater
- arrange / assert later / act の順となっている
-
emitsInOrder
- リスト順にemitされるのを検証するMatcher
test.dart
test(
'should emit [Error] when the input is invalid',
() async {
// arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Left(InvalidInputFailure()));
// assert later
final expected = [
// flutter_bloc 最新versionではInitialStateがないため削除
// Empty(),
Error(message: INVALID_INPUT_FAILURE_MESSAGE),
];
// 注: チュートリアルでは動かないので修正
// bloc.state -> bloc
// 動かない例: expectLater(bloc.state, emitsInOrder(expected));
expectLater(bloc, emitsInOrder(expected));
// act
bloc.dispatch(GetTriviaForConcreteNumber(tNumberString));
},
);
implementation.dart
@override
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
yield* inputEither.fold(
(failure) async* {
yield Error(message: INVALID_INPUT_FAILURE_MESSAGE);
},
// Although the "success case" doesn't interest us with the current test,
// we still have to handle it somehow.
(integer) => throw UnimplementedError(),
);
}
}
mapEventToState
- Eventに応じたStateを返すメソッド
- yield の再帰形である yield*を返す
- UseCase(InputConverter)から
Either<Failure, int>
を受けとる-
fold
で分岐させることでRightとLeftの両方を処理する- これがRepository以降で扱ったEitherの終着点となっている
-
再帰的にStateを返す例
-
Loading, Loaded
のStateを返す例
test.dart
test(
'should emit [Loading, Loaded] when data is gotten successfully',
() async {
// arrange
setUpMockInputConverterSuccess();
when(mockGetConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// assert later
final expected = [
Loading(),
Loaded(trivia: tNumberTrivia),
];
expectLater(bloc.state, emitsInOrder(expected));
// act
bloc.dispatch(GetTriviaForConcreteNumber(tNumberString));
},
);
implementation.dart
@override
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
yield* inputEither.fold(
(failure) async* {
yield Error(message: INVALID_INPUT_FAILURE_MESSAGE);
},
(integer) async* {
yield Loading();
final failureOrTrivia = await getConcreteNumberTrivia(
Params(number: integer),
);
yield failureOrTrivia.fold(
(failure) => throw UnimplementedError(),
(trivia) => Loaded(trivia: trivia),
);
},
);
}
}
学んだこと
- Streamのテスト手法
await untilCalled()
expectLater
emitsInOrder
- Eitherの
fold()
メソッドを使用することで、Eitherから抜ける - FailureのサブクラスはBLoC内ですべて処理する
- ErrorメッセージをViewで表示するのがゴールとなる
アドベントカレンダー完了
- 12/1に思いついてFlutterのアドベントカレンダーをはじめました。
- 私自身はプログラミング歴は浅く、どちらかというとディレクターやPM、開発人事などをやっていました
- ただプログラミングをやろうと決めて、Flutterをはじめました。
- アウトプットすると良いという話を聞いてアドベントカレンダーをやってみたら、自身に何度も説明するような感じで理解が深まったと思います。
- ただ、思いついて始めたのでネタがなかったです。途中でチュートリアルに頼りました。
- それでも毎日続けるのが大事と思った次第です。一日でも途切れるとそれっきりになってしまいそうだったので、続けられる手段としてチュートリアル実況みたいにしました。
これから
- チュートリアルはまだあと数回あるので、Qiita記事にします。
- その後も色々とQiitaを書くつもりです。
- ただ、毎日執筆はしないと思います。
- 少しずつ質重視にシフトできたらいいなと思います。
- 以上です。ありがとうございました☆