1. 개요
mocktail을 이용하여 테스트 코드를 작성하던 중 마주친 오류와 해결 방안에 대해 서술했습니다.
2. 문제 상황
현재 제 아키텍처는 다음과 같은 계층 구조로 데이터가 흐릅니다.
DataSource -> Repository -> UseCase -> ViewModel
먼저 DataSource에서 외부 API와 통신을 하고,
그 결과를 Repository에 반환한 후,
UseCase, ViewModel로 이어지는 구조입니다.
문제는 Repository 테스트 코드를 작성하던 중 다음과 같은 오류가 발생했습니다.
Bad state: A test tried to use `any` or `captureAny` on a parameter of type `DreamDto`, but
registerFallbackValue was not previously called to register a fallback value for `DreamDto`.
해석하자면 DreamDto 타입의 인자에 대해 any 또는 captureAny를 사용하려 했지만, mocktail이 해당 타입의 더미 인스턴스를 생성할 수 없기 때문에 registerFallbackValue를 통해 명시적으로 fallback 객체를 등록해줘야 한다는 뜻입니다.
any와 capture에 대한 설명은 더보기를 클릭해주세요.
mocktail로 테스트할 때 확인하고 싶은게 있을 것입니다.
1. 이 메서드가 실제로 호출됐는지 확인하고 싶다.
2. 메서드에 무슨 값이 전달됐는지 알고 싶다.
3. 값이 뭐든 상관없이 일단 호출됐는지만 확인하고 싶다.
이때 사용하는 도구가 바로 any와 captureAny입니다.
any<T>()는 T 타입의 어떤 값이든 허용되는 인자로 인자값이 무엇인지에는 관심이 없고, 메서드가 호출되었는지만 확인하고 싶을 때 사용합니다.
captureAny<T>()는 메서드 호출 시 전달된 실제 인자 값을 캡처해서 나중에 확인할 수 있는 matcher로, 어떤 값이 전달되었는지 확인하고 싶을 때 사용합니다.
제 코드에서 문제가 되는 곳은 이 부분이었습니다.
test('returns the uid of the dream when saving is successful', () async {
// Arrange
when(
() => dreamSaveDataSource!.saveDream(any()),
).thenAnswer((_) async => 1);
// Act
final response = await remoteDreamRepository?.saveDream(dream);
// Assert
expect(response, 1);
verify(() => dreamSaveDataSource!.saveDream(any())).called(1);
});
꿈 저장에 성공하면 uid를 반환값으로 돌려주는 코드가 제대로 실행되었는지 테스트하는 코드입니다.
여기서 유의깊게 봐야할 점은 4번째 줄입니다.
when(() => dreamSaveDataSource!.saveDream(any()))
dreamSaveDataSource의 saveDream 메서드는 인자값이 DreamDto 타입이 들어갑니다.
이 부분을 any()라고 설정한 것이 문제가 되었습니다.
any나 captureAny를 사용할 때는 T 타입의 유효한 객체를 내부적으로 만들어야하기 때문에 T가 사용자 정의 타입일 경우, 명시적으로 등록된 더미 객체가 필요했습니다.
DreamDto는 사용자 정의 타입이기 때문에 mocktail이 임의로 객체를 생성할 수 없어 에러가 발생한 것입니다.
3. 해결 방안
테스트가 실패했을 때 TEST RESULTS를 보면 해결 방법이 친절하게 제시되어 있습니다.
Fake를 상속받은 Fake 객체를 만들고 이를 SetUpAll로 더미 객체를 등록하는 방법입니다.
class MyTypeFake extends Fake implements MyType {}
void main() {
setUpAll(() {
registerFallbackValue(MyTypeFake());
});
}
저는 필요한 FakeDreamDto를 만들고 이를 registerFallbackValue에 등록해서 해결했습니다.
class FakeDreamDto extends Fake implements DreamDto {}
void main() {
setUpAll(() {
registerFallbackValue(FakeDreamDto());
});
}
이렇게 하면 mocktail은 any<DreamDto>()를 사용할 때 내부적으로 FakeDreamDto 인스턴스를 사용하게 되어 테스트가 정상적으로 작동합니다.
'Flutter' 카테고리의 다른 글
| [Flutter] TMDB로 영화관 앱 만들기 (0) | 2025.05.15 |
|---|---|
| [Flutter] 기능 우선 분리(Feature first Structure)로 영화관 앱 구조 설계하기 (0) | 2025.05.15 |
| [Flutter] mocktail을 이용하여 Unit Test 하기 (mockito vs mocktail) (0) | 2025.05.08 |
| [Flutter] 앱 테스트 유형 (feat. Integration, Unit, Widget) (0) | 2025.05.07 |
| [Flutter] Riverpod으로 상태관리하고, MVVM패턴 적용해서 Counter앱 만들기 (0) | 2025.05.02 |