이번 자취yum! 프로젝트에서 Mockito를 이용하여 단위테스트를 진행하다가 위 제목과 같은 에러를 마주했습니다.
([IngredientRepository])
main() {
late final IngredientRepository ingredientRepository;
late final IngredientViewModelImpl ingredientViewModel;
...(생략)
group("Ingredient View Model Unit Test", () {
setUpAll(() {
ingredientRepository = MockIngredientRepository();
ingredientViewModel =
IngredientViewModelImpl(ingredientRepository: ingredientRepository);
});
이 메시지를 보고 문제가 되는 Repository 클래스를 들락날락하면서 문제가 무엇인지 생각해보았습니다.
class IngredientRepositoryImpl implements IngredientRepository {
final RemoteDatasource remoteDatasource;
IngredientRepositoryImpl({required this.remoteDatasource});
/// 나의 냉장고 재료 조회 Api
Future<List<Ingredient>> getMyIngredient() async {
return remoteDatasource.getMyIngredient().then((response) =>
response.map((json) => Ingredient.fromJson(json)).toList());
}
/// 나의 재료 생성 Api
Future<Ingredient> createNewIngredient(Ingredient ingredient) async {
final response =
await remoteDatasource.createNewIngredient(ingredient.toJson());
return Ingredient.fromJson(response);
}
}
abstract class IngredientRepository {
Future<List<Ingredient>> getMyIngredient();
Future<Ingredient> createNewIngredient(Ingredient ingredient);
}
아무리 봐도 정상이죠. 한참을 고민하다가 보니 setUpAll 메소드 자체가 실패하는 것을 확인했습니다.
초기화부터 안된다는 것을 의미했습니다. 여기서, ViewModel 클래스를 다시 떠올렸습니다.
class IngredientViewModelImpl extends ChangeNotifier
implements IngredientViewModel {
final IngredientRepository ingredientRepository;
List<Ingredient> _myIngredients = List.empty();
...(생략)
IngredientViewModelImpl({required this.ingredientRepository}) {
fetchData();
}
Future<void> fetchData() async {
try {
final result = await ingredientRepository.getMyIngredient();
_myIngredients = result;
notifyListeners();
} on Exception catch (e) {
// 예를 들어, 에러상황에서는 토스트 메시지를 띄워서 사용자에게 알림을 보냄.
throw Exception("재료 불러오기 에러");
}
}
이상한게 느껴지시나요?? ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
그렇습니다... ViewModel이 생성되면서 fetchData()함수를 호출합니다.이제 다시, 문제의 setUpAll 함수를 보겠습니다.
group("Ingredient View Model Unit Test", () {
setUpAll(() {
ingredientRepository = MockIngredientRepository();
ingredientViewModel =
IngredientViewModelImpl(ingredientRepository: ingredientRepository);
});
제가 ViewModel에 전달하는 ingredientRepository는 MockIngredientRepository로 build_runner가 생성한 Mock 클래스입니다. Mock 클래스이기에 when을 통해서 제가 함수를 설정할 수 있습니다.
반대로 얘기하면, 제가 설정하지 않으면 해당 메소드를 찾지 못하는 것이죠.
즉, fetchData()를 미리 정의하지 않았기에 getMyIngredient()함수를 찾지 못하여 저 에러를 뱉어낸 것이었습니다.
group("Ingredient View Model Unit Test", () {
setUpAll(() {
ingredientRepository = MockIngredientRepository();
when(ingredientRepository.getMyIngredient()).thenAnswer(
(_) async => [...freezedIngredients, ...unfreezedIngredients]);
ingredientViewModel =
IngredientViewModelImpl(ingredientRepository: ingredientRepository);
});
자, fetchData()를 지정하니 이젠 다시 성공적으로 테스트가 수행되는 것을 확인할 수 있습니다.
TDD에 대해서 배우는게 많은 하루입니다.