이번에는 지난번 작성한 유스케이스(CreateUser
)의 테스트 코드를 작성해보며, 테스트 코드의 구조인 AAA 패턴과 mock 객체(Mocktail) 사용법을 정리해보려 한다.
참고를 위한 유스케이스 코드 (create_user.dart
)
class CreateUser extends UsecaseWithParams<void, CreateUserParams> {
const CreateUser(this._repository);
final AuthenticationRepository _repository;
ResultVoid call(CreateUserParams params) async =>
_repository.createUser(
createdAt: params.createdAt,
name: params.name,
avatar: params.avatar,
);
}
class CreateUserParams extends Equatable {
final String createdAt;
final String name;
final String avatar;
const CreateUserParams.empty() :
this(createdAt: '_empty.createdAt', name: '_empty.name', avatar: '_empty.avatar');
const CreateUserParams({
required this.createdAt,
required this.name,
required this.avatar,
});
List<Object?> get props => [createdAt, name, avatar];
}
들어가기에 앞서,
플러터 프로젝트에서 테스트 코드를 작성하기 위해 유용한 플러그인 Dart Test 을 설치한다.
이걸 사용하면 테스트하고자 하는 파일을 우클릭하여 Create Dart test file
을 클릭하면 테스트 폴더 내에 테스트 파일을 손쉽게 생성할 수 있다.
테스트 코드 작성을 위해 Mocktail 패키지를 설치한다.
모킹을 위해 Mocktail 또는 Mockito 을 사용할 수 있다.
Mocktail
과 Mockito
는 둘 다 Dart/Flutter에서 테스트용 가짜 객체(mock)를 만드는(=모킹하는) 라이브러리이다.
Mocktail
은 Mochkito
보다 더 간단하다. Mockito
는 build_runner 을 써서 코드 생성이 필요하지만, Mocktail
은 코드 생성 없이 바로 쓸 수 있어서 요즘엔 Mocktail을 더 많이 사용한다.
터미널에 아래의 명령어를 입력해서 설치해준다.
flutter pub add -d mocktail && flutter pub get
테스트 코드는 AAA 패턴(Arrange - Act - Assert)에 따라 작성하는 것이 일반적이다.
이 구조는 테스트의 흐름을 명확하게 하고, 가독성과 유지보수성을 높여줄 수 있다.
단계 | 의미 | 설명 |
---|---|---|
Arrange | 준비 | 테스트에 필요한 Mock, Stub 등 설정 |
Act | 실행 | 테스트 대상 메서드 또는 클래스 실행 |
Assert | 검증 | 기대한 결과가 실제와 일치하는지 확인 |
해당 구조를 지키며 테스트 코드를 다음처럼 작성하였다(전체 코드)
class MockAuthRepo extends Mock implements AuthenticationRepository {}
void main() {
late CreateUser usecase;
late AuthenticationRepository repository;
setUpAll(() {
repository = MockAuthRepo();
usecase = CreateUser(repository);
});
final params = CreateUserParams.empty();
test('should call the [Repository.createUser]', () async {
// Arrange
when(
() => repository.createUser(
createdAt: any(named: 'createdAt'),
name: any(named: 'name'),
avatar: any(named: 'avatar'),
),
).thenAnswer((_) async => const Right(null));
// Act
final result = await usecase(params);
// Assert
expect(result, equals(const Right<Failure, void>(null)));
verify(
() => repository.createUser(
createdAt: params.createdAt,
name: params.name,
avatar: params.avatar,
),
).called(1);
verifyNoMoreInteractions(repository);
});
}
우선, 리포지토리의 모킹 클래스를 정의해야한다. mocktail
패키지에서 제공하는 Mock
클래스를 상속하여, 우리가 테스트할 Repository의 가짜 버전을 만들 수 있다. 이는 실제 AuthenticationRepository
를 구현할 필요 없이, 테스트용으로만 사용하는 가벼운 객체이다.
class MockAuthRepo extends Mock implements AuthenticationRepository {}
다음으로는 초기화이다. setUpAll
은 테스트 전체에서 한 번만 실행되는 초기화 블록이다.
여기서 테스트 대상인 CreateUser
는 실제 Repository 대신 MockAuthRepo
를 주입받으면 된다. (의존성 주입).
setUpAll(() {
repository = MockAuthRepo();
usecase = CreateUser(repository);
});
파라미터도 정의해준다. 해당 파라미터는 CreateUserParams 에 비어있는 객체를 생성하는 메서드를 따로 만들어줬다.
final params = CreateUserParams.empty();
create_user.dart
const CreateUserParams.empty() :
this(createdAt: '_empty.createdAt', name: '_empty.name', avatar: '_empty.avatar');
이제 Arrange 과정으로 Stubbing(행동 정의) 을 진행한다.
when(...).thenAnswer(...)
를 통해 가짜 Repository가 어떻게 행동할지를 지정한다.any(named: ...)
는 매개변수 값에 상관없이 허용한다는 의미이다.Right(null)
은 성공적인 결과를 의미한다. (Either<Failure, void>
).when(
() => repository.createUser(
createdAt: any(named: 'createdAt'),
name: any(named: 'name'),
avatar: any(named: 'avatar'),
),
).thenAnswer((_) async => const Right(null));
정리하자면 when(...).thenAnswer(...)
을 통해 "만약에 repository.createUser(...)가 호출된다면, 이렇게 응답해!" 라고 설정할 수 있다.
인자에 있는 any(named: '...')
는 특정 값이 들어오지 않아도 괜찮다는 뜻으로 테스트에서 값 자체가 중요한 게 아니라 "호출되었는지 여부" 가 중요하니까, 어떤 값이 들어오든 허용한다는 뜻이다. thenAnswer((_) async => const Right(null))
을 통해 이 메서드가 실제로 호출되면 성공을 의미하는 값(Right) 을 비동기적으로 돌려준다.
Right(null)은 "성공했고, 별도의 리턴값은 없다"는 뜻.
(Either<Failure, void> 구조에서 성공 쪽이 Right)
다음으로 실제 테스트 대상(유스케이스)을 실행한다 (Act)
final result = await usecase(params);
마지막으로 결과 검증 및 호출 여부를 확인한다. (Assert)
expect(result, equals(const Right<Failure, void>(null)));
결과가 예상한 성공 결과와 같은지 검증한다.
called(1) 을 통해 createUser
가 정확히 한 번 호출되었는지 확인할 수 있다.
verify(() => repository.createUser(...)).called(1);
그리고, verifyNoMoreInteractions
을 통해 더 이상 다른 메서드가 호출되지 않았는지도 검증한다.
verifyNoMoreInteractions(repository);
테스트는 Run 'tests in create_user` 을 눌러서 테스트할 수 있다.
테스트 결과는 다음처럼 Tests passed: 1 으로 확인할 수 있다.