[Flutter]No method stub was called from within `when()`. Was a real method called, or perhaps an extension method? 해결 기록

한상욱·2024년 12월 2일
0

에러해결모음

목록 보기
5/10
post-thumbnail

사건의 발단

이번 자취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에 대해서 배우는게 많은 하루입니다.

profile
자기주도적, 지속 성장하는 모바일앱 개발자의 기록

0개의 댓글