search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

Flutter TDD Clean Architecture Course その11, 12 - Blocの実装


  • 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を書くつもりです。
  • ただ、毎日執筆はしないと思います。
    • 少しずつ質重視にシフトできたらいいなと思います。
  • 以上です。ありがとうございました☆

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
What you can do with signing up
1