[Flutter] : TDD - 03.Domain Layer Refactoring


이전 내용에서 작성하였던 Domain 레이어를 리펙토링 해보겠다.

먼저 알아 두면 좋은 Dart언어의 특징 중 하나는

Object 내부 call 함수를 2가지 방식으로 호출 할 수 있다는 것이다.

Object.call();

Object();

call 함수만 가능

이제 다시 저번에 작성한 우리 usecase코드와 test코드를 확인해보자.

class GetConcreteNumberTrivia {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

  Future<Either<Failure, NumberTrivia>> excute({@required int number}) async {
    return await repository.getConcreteNumberTrivia(number);
  }
}
void main() {
...

  test('should get tirivia for the number from the repository', () async {
    // arrange
    when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
        .thenAnswer((_) async => Right(tNumberTirivia));
    // act , 아직 구현되지 않음 함수
    final result = await usecase.excute(number: tNumber);
    // assert
    expect(result, Right(tNumberTirivia));
    // Repository에서 함수가 호출되었는지 확인
    verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
    // 위의 방법만 호출하고 더 이상 호출하면 안된다.
    verifyNoMoreInteractions(mockNumberTriviaRepository);
  });

}

여기서 usecase.excute를 usecase.call로 즉 usecase()로 호출 할 수 있다.

그리고 모든 usecase는 call 함수가 필요하다. 즉 인터페이스로 만들자.

수정 해보자.

// core/usecases/usecase.dart

import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:number_trivia/core/error/failures.dart';

abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

class NoParams extends Equatable {}

공통 인터페이스를 만들고 이제 이걸로 수정해보자.

domain/usecases/get_concrete_number_trivia.dart

class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

  Future<Either<Failure, NumberTrivia>> call(Params params) async {
    return await repository.getConcreteNumberTrivia(params.number);
  }
}

class Params extends Equatable {
  final int number;

  Params({@required this.number});

  @override
  List<Object> get props => [number];
}

equatable 최신 버전에서는 생성자에 값을 넣어주는 것이 아니라 get props 함수를 구현해주어야 비교 연산이 동작한다. 이전 코드들도 수정하자.

test/domain/usecases/get_concrete_number_trivia_test.dart

...
final result = await usecase(Params(number: tNumber));
...

정상적으로 테스트가 돌아간다!

이제 get_random_number_trivia_test를 구현해보자.

TDD 개발론 처럼 먼저 test코드를 작성하자.

class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  GetRandomNumberTrivia usecase;
  MockNumberTriviaRepository mockNumberTriviaRepository;

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

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

  test('should get trivia from the repository', () async {
    //arrange
    when(mockNumberTriviaRepository.getRandomNumberTrivia())
        .thenAnswer((_) async => Right(tNumberTrivia));
    //act
    // 랜덤 숫자는 파라미터가 필요없다 그냥 넘겨주자
    final result = await usecase(NoParams());
    //assert
    expect(result, Right(tNumberTrivia));
    verify(mockNumberTriviaRepository.getRandomNumberTrivia());
    verifyNoMoreInteractions(mockNumberTriviaRepository);
  });
}

이제 usecase를 구현해보자.

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

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

이제 테스트 코드를 동작시키면 정상적으로 잘 돌아간다.