Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The "when" function does not create a stub #199

Closed
InAnadea opened this issue Jun 14, 2023 · 5 comments
Closed

The "when" function does not create a stub #199

InAnadea opened this issue Jun 14, 2023 · 5 comments
Labels
question Further information is requested

Comments

@InAnadea
Copy link

Bug description
I'm trying to mock the use case for the bloc. But I got the error: Bad state: No method stub was called from within when(). Was a real method called, or perhaps an extension method?

Steps to reproduce the behavior:

import 'package:bloc_test/bloc_test.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:imagilabs_edu_flutter/data/models/admin/teacher_admin_model.dart';
import 'package:imagilabs_edu_flutter/data/models/admin/teacher_subscription_model.dart';
import 'package:imagilabs_edu_flutter/domain/usecases/get_google_sheet_link_usecase.dart';
import 'package:imagilabs_edu_flutter/domain/usecases/search_teachers_usecase.dart';
import 'package:imagilabs_edu_flutter/presentation/screens/admin/teachers/bloc/admin_teachers_screen_bloc.dart';
import 'package:imagilabs_edu_flutter/util/constants.dart';
import 'package:mocktail/mocktail.dart';

class MockSearchTeachersUsecase extends Mock implements SearchTeachersUsecase {}

class MockGetGoogleSheetLinkUsecase extends Mock
    implements GetGoogleSheetLinkUsecase {}

void main() {
  late AdminTeachersScreenBloc sut;
  late GetGoogleSheetLinkUsecase getGoogleSheetLink;
  late SearchTeachersUsecase searchTeachers;

  setUp(() {
    getGoogleSheetLink = MockGetGoogleSheetLinkUsecase();
    searchTeachers = MockSearchTeachersUsecase();
    sut = AdminTeachersScreenBloc(searchTeachers, getGoogleSheetLink);
  });

  const userId = 'id';
  const userEmail = '[email protected]';
  const firstName = 'firstName';
  const lastName = 'lastName';
  const country = 'country';
  const organization = 'Test organization';

  const teacher = TeacherAdminModel(
    id: userId,
    email: userEmail,
    firstName: firstName,
    lastName: lastName,
    country: country,
    organization: organization,
    howDidYouHearAboutUs: 'howDidYouHearAboutUs',
    emailVerified: true,
    createdAt: 0,
    lastModifiedAt: 0,
    subscription: TeacherSubscriptionModel(
      start: 0,
      end: 0,
      plan: SubscriptionPlan.pro,
      canceled: false,
    ),
  );
  const teachers = [teacher];
  const sortingDirection = SortingDirection.asc;
  const sortingType = SortingType.createdAt;
  const searchQuery = '';
  const searchParams = SearchTeachersParams(
    sortingDirection: sortingDirection,
    sortingType: sortingType,
    searchQuery: searchQuery,
  );
  const sheetLink = 'google.com';

  blocTest( // it works
    'emits [] when nothing is added.',
    build: () => sut,
    expect: () => [],
  );

  blocTest( // it doesn't work
    'emits [loading, updated] when init is added.',
    build: () => sut,
    setUp: () {
      // this is default sorting params for view initialization
      when(() => searchTeachers(const SearchTeachersParams(
            sortingDirection: SortingDirection.desc,
            sortingType: SortingType.createdAt,
          ))).thenAnswer((_) async => const Right(teachers));
      when(() => getGoogleSheetLink())
          .thenAnswer((_) async => const Right(sheetLink));
    },
    act: (bloc) => bloc.add(const AdminTeachersScreenEvent.init()),
    expect: () => [
      isA<LoadingAdminTeachersScreenState>(),
      isA<UpdatedAdminTeachersScreenState>(),
    ],
  );

  blocTest( // it doesn't work
    'emits [updated] when search is added.',
    build: () => sut,
    seed: () => const AdminTeachersScreenState.initial(),
    setUp: () {
      when(() => searchTeachers(searchParams))
          .thenAnswer((_) async => const Right(teachers));
      when(() => getGoogleSheetLink())
          .thenAnswer((_) async => const Right(sheetLink));
    },
    act: (bloc) => bloc.add(const AdminTeachersScreenEvent.search(
      sortingDirection: sortingDirection,
      sortingType: sortingType,
      searchQuery: searchQuery,
    )),
    expect: () => [
      isA<LoadingAdminTeachersScreenState>(),
      isA<UpdatedAdminTeachersScreenState>(),
    ],
  );
}

The source code of classes:

import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:imagilabs_edu_flutter/data/models/admin/teacher_admin_model.dart';
import 'package:imagilabs_edu_flutter/domain/usecases/get_google_sheet_link_usecase.dart';
import 'package:imagilabs_edu_flutter/domain/usecases/search_teachers_usecase.dart';
import 'package:imagilabs_edu_flutter/util/bloc/notifiable_bloc.dart';
import 'package:imagilabs_edu_flutter/util/constants.dart';
import 'package:imagilabs_edu_flutter/util/errors/failures.dart';
import 'package:url_launcher/url_launcher_string.dart';

part 'admin_teachers_screen_event.dart';
part 'admin_teachers_screen_state.dart';
part 'admin_teachers_screen_notification.dart';
part 'admin_teachers_screen_bloc.freezed.dart';

class AdminTeachersScreenBloc extends NotifiableBloc<AdminTeachersScreenEvent,
    AdminTeachersScreenState, AdminTeachersScreenNotification> {
  final SearchTeachersUsecase _searchTeachers;
  final GetGoogleSheetLinkUsecase _getGoogleSheetLink;

  AdminTeachersScreenBloc(
    this._searchTeachers,
    this._getGoogleSheetLink,
  ) : super(const AdminTeachersScreenState.initial()) {
    on<_Init>(_onInit);
    on<_Search>(_onSearch);
    on<_OpenGoogleSheetLink>(_onOpenGoogleSheetLink);
  }

  Future<void> _onInit(
    _Init event,
    Emitter<AdminTeachersScreenState> emit,
  ) async {
    emit(const AdminTeachersScreenState.loading());

    await _fetchDataAndUpdate(emit);
  }

  Future<void> _onSearch(
    _Search event,
    Emitter<AdminTeachersScreenState> emit,
  ) async {
    await _fetchDataAndUpdate(
      emit,
      sortingType: event.sortingType,
      sortingDirection: event.sortingDirection,
      searchQuery: event.searchQuery,
    );
  }

  Future<void> _fetchDataAndUpdate(
    Emitter<AdminTeachersScreenState> emit, {
    SortingType sortingType = SortingType.createdAt,
    SortingDirection sortingDirection = SortingDirection.desc,
    String? searchQuery,
  }) async {
    final teachersOrFailureFuture = _searchTeachers(SearchTeachersParams(
      sortingType: sortingType,
      sortingDirection: sortingDirection,
      searchQuery: searchQuery,
    ));
    final googleSheetLinkOrFailureFuture = _getGoogleSheetLink();

    await Future.wait([
      teachersOrFailureFuture,
      googleSheetLinkOrFailureFuture,
    ]);

    final teachersOrFailure = await teachersOrFailureFuture;
    final googleSheetLinkOrFailure = await googleSheetLinkOrFailureFuture;

    ifFailure(failure) => AdminTeachersScreenState.failure(failure);
    emit(teachersOrFailure.fold(
      ifFailure,
      (teachers) => googleSheetLinkOrFailure.fold(
        ifFailure,
        (link) => AdminTeachersScreenState.updated(
          teachers: teachers,
          googleSheetLink: link,
        ),
      ),
    ));
  }

  Future<void> _onOpenGoogleSheetLink(
    _OpenGoogleSheetLink event,
    Emitter<AdminTeachersScreenState> emit,
  ) async {
    try {
      if (await canLaunchUrlString(event.link)) {
        await launchUrlString(event.link);
      } else {
        emitNotification(const AdminTeachersScreenNotification.error());
      }
    } catch (exc) {
      emitNotification(AdminTeachersScreenNotification.error(exc));
    }
  }
}
import 'package:dartz/dartz.dart';
import 'package:imagilabs_edu_flutter/util/errors/failures.dart';
import 'package:imagilabs_edu_flutter/util/usecase.dart';

abstract class GetGoogleSheetLinkUsecase extends Usecase<String, NoParams> {
  @override
  Future<Either<Failure, String>> call([NoParams params]);
}
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:imagilabs_edu_flutter/data/models/admin/teacher_admin_model.dart';
import 'package:imagilabs_edu_flutter/util/constants.dart';
import 'package:imagilabs_edu_flutter/util/errors/failures.dart';
import 'package:imagilabs_edu_flutter/util/usecase.dart';

part 'search_teachers_usecase.freezed.dart';

abstract class SearchTeachersUsecase
    extends Usecase<List<TeacherAdminModel>, SearchTeachersParams> {
  @override
  Future<Either<Failure, List<TeacherAdminModel>>> call(
    SearchTeachersParams params,
  );
}

@freezed
class SearchTeachersParams with _$SearchTeachersParams {
  const factory SearchTeachersParams({
    String? searchQuery,
    required SortingDirection sortingDirection,
    required SortingType sortingType,
  }) = _SearchTeachersParams;
}

And I get an error:

Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an extension method?
package:mocktail/src/mocktail.dart 261:7                                                  When._completeWhen
package:mocktail/src/mocktail.dart 256:12                                                 When.thenAnswer
test/presentation/screens/admin/teachers/bloc/admin_teachers_screen_bloc_test.dart 80:12  main.<fn>
package:bloc_test/src/bloc_test.dart 201:20                                               testBloc.<fn>
package:bloc_test/src/bloc_test.dart 254:15                                               _runZonedGuarded.<fn>
dart:async                                                                                runZonedGuarded
package:bloc_test/src/bloc_test.dart 253:3                                                _runZonedGuarded
package:bloc_test/src/bloc_test.dart 200:11                                               testBloc
package:bloc_test/src/bloc_test.dart 156:13                                               blocTest.<fn>
===== asynchronous gap ===========================
dart:async                                                                                _Completer.completeError
package:bloc_test/src/bloc_test.dart 257:43                                               _runZonedGuarded.<fn>
===== asynchronous gap ===========================
dart:async                                                                                _CustomZone.registerBinaryCallback
package:bloc_test/src/bloc_test.dart 254:5                                                _runZonedGuarded.<fn>
dart:async                                                                                runZonedGuarded
package:bloc_test/src/bloc_test.dart 253:3                                                _runZonedGuarded
package:bloc_test/src/bloc_test.dart 200:11                                               testBloc
package:bloc_test/src/bloc_test.dart 156:13                                               blocTest.<fn>

Expected behavior
The "when" function should mock methods.

Logs

Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel stable, 3.10.4, on macOS 13.2.1 22D68 darwin-arm64, locale en-GB)
    ! Warning: `flutter` on your path resolves to /Users/ivan/fvm/versions/3.10.2/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/ivan/fvm/versions/3.10.4. Consider adding /Users/ivan/fvm/versions/3.10.4/bin to the front of your path.
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/2.18.6/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/ivan/fvm/versions/3.10.4. Consider adding /Users/ivan/fvm/versions/3.10.4/bin to the front of your path.
[✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.2)
[✓] Android Studio (version 2022.1)
[✓] IntelliJ IDEA Community Edition (version 2022.2.3)
[✓] IntelliJ IDEA Community Edition (version 2023.1.1)
[✓] VS Code (version 1.78.2)
[✓] Connected device (2 available)
[✓] Network resources

! Doctor found issues in 1 category.
@ihorkozar
Copy link

Any updates ?

@MarlonDSC
Copy link

I'm also facing this error

@cromueloliver02
Copy link

I'm facing this error as well, any updates?

@felangel
Copy link
Owner

Hi @InAnadea 👋
Can you please share a link to a minimal reproduction sample? It's hard to help without being able to reproduce the issue locally, thanks!

@felangel felangel added question Further information is requested waiting for response Waiting for follow up labels Apr 20, 2024
@felangel
Copy link
Owner

Closing for now since this issue is quite old and there isn't a minimal reproduction sample. If this is still a problem please file a new issue with a link to a reproduction sample, thanks!

@felangel felangel removed the waiting for response Waiting for follow up label Jun 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants