search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

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


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: 検証する

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