3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

わたしのFlutterお勉強用Advent Calendar 2020

Day 17

FlutterでClean ArchitectureとTDDをやってみる その3 - Domain層のリファクタリング

Last updated at Posted at 2020-12-17

call()メソッド

  • call()を定義したクラスについて、object.call()object()とも書ける
    • UseCaseを使う際に便利
get_concrete_number_trivia_test.dart
// before
// final result = await usecase.execute(number: tNumber);

// after
final result = await usecase(number: tNumber);

2つめのUseCaseGetRandomNumberTriviaを追加する

UseCaseのBaseClass

  • cleanなコーディングのためにはBaseClassを作るとよい
  • UseCaseはcallメソッドを作るとわかりやすい
    • method名を覚えなくて良いため
  • 以下のようなBase Classを用いる
    • call()にパラメータを使わない場合にはNoParamsを用いる
      • getRandomNumberTriviaの場合、パラメータを用いないため
usecase.dart
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import '../error/failure.dart';

// method定義
// Type, ParamsはGenericsである。成功の場合の型をType、受け取るパラメータの型をParamsとして記述している
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

// parameterを用いない場合に使うダミーのparameter
class NoParams extends Equatable {}

BaseClassの継承

  • GetConcreteNumberTriviaにBaseClassをextendsさせる
  • int numberを渡せるParamsクラスを作成する
    • これはintのまま渡してもよい。複数のパラメータを渡す場合はこのクラスを使うのが良いと思う
get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
  ...
}

class Params extends Equatable {
  final int number;

  Params({@required this.number}) : super([number]);
}
  • testをリファクタリングする
    • executeを省略する
    • Paramsを使用する
    • 実装が対応していないため、このテストは失敗する。
get_concrete_number_trivia_test.dart
...
test(
  'should get trivia for the number from the repository',
  () async {
    // arrange
    when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
        .thenAnswer((_) async => Right(tNumberTrivia));
    // act
    final result = await usecase(Params(number: tNumber));
    // assert
    expect(result, Right(tNumberTrivia));
    verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
    verifyNoMoreInteractions(mockNumberTriviaRepository);
  },
);
  • 実装を修正する
get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
  ...
  @override
  Future<Either<Failure, NumberTrivia>> call(Params params) async {
    return await repository.getConcreteNumberTrivia(params.number);
  }
}
...

GetRandomNumberTrivia

get_random_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/core/usecase/usecase.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  GetRandomNumberTrivia usecase;
  MockNumberTriviaRepository mockNumberTriviaRepository;

  setUp(() {
    mockNumberTriviaRepository = MockNumberTriviaRepository();
    usecase = GetRandomNumberTrivia(mockNumberTriviaRepository);
  });

  final tNumberTrivia = NumberTrivia(number: 1, text: 'test');

  test(
    'should get trivia from the repository',
    () async {
      // arrange
      when(mockNumberTriviaRepository.getRandomNumberTrivia())
          .thenAnswer((_) async => Right(tNumberTrivia));
      // act
      // Since random number doesn't require any parameters, we pass in NoParams.
      final result = await usecase(NoParams());
      // assert
      expect(result, Right(tNumberTrivia));
      verify(mockNumberTriviaRepository.getRandomNumberTrivia());
      verifyNoMoreInteractions(mockNumberTriviaRepository);
    },
  );
}
  • 実装する
get_random_number_trivia.dart
import 'package:dartz/dartz.dart';

import '../../../../core/error/failure.dart';
import '../../../../core/usecase/usecase.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';

class GetRandomNumberTrivia extends UseCase<NumberTrivia, NoParams> {
  final NumberTriviaRepository repository;

  GetRandomNumberTrivia(this.repository);

  @override
  Future<Either<Failure, NumberTrivia>> call(NoParams params) async {
    return await repository.getRandomNumberTrivia();
  }
}

学んだこと

  • UseCaseが複数ある場合はインターフェイスを作るときれいに設計できる

  • call()メソッドを使うことでインスタンスを関数化できるのが便利

  • 複数UseCaseで異なる引数をとる場合、Genericsを使う

    • 引数がない場合のダミーとして NoParams を使うのがよい
    • NoParamsはインターフェイスのファイルに記述しておけば、UseCaseの実装から呼べる
    • GetConcreteNumberTrivia用のParamsは同ファイルに記述すれば、同UseCaseを呼ぶファイルで同じく呼べる
  • Testを次の3段階に記述するのは覚えておくと何をするか迷わなくてよい

    • arrange: mockの作成、DI等の準備、mockの挙動設定
    • act: テスト対象メソッドを呼ぶ
    • assert: 検証する
3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?