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

when method throwing error type 'Null' is not a subtype of type ... #71

Closed
jrmallorca opened this issue Aug 29, 2021 · 1 comment
Closed

Comments

@jrmallorca
Copy link

Description
I am trying to implement Reso Coder's Clean Architecture in my own project and have come across this problem while testing. Using the when method from Mocktail would throw out the error: type 'Null' is not a subtype of type 'Future<Either<Failure, List<WorkoutEntity>>>'. A minimal reproduction will be given below.

I've confirmed it's not a bloc_test issue as the last test still does not pass.

Steps To Reproduce

  1. flutter create bug
  2. Copy and paste the following 'pubspec.yaml' file:
pubspec.yaml
name: bug
description: A new Flutter project.

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  # Functional programming
  dartz: ^0.10.0-nullsafety.2
  # BLoC (State management)
  flutter_bloc: ^7.1.0
  # Boilerplate code
  equatable: ^2.0.3

dev_dependencies:
  flutter_test:
    sdk: flutter
  # Testing for BLoC
  bloc_test: ^8.1.0
  # Mock testing
  mocktail: ^0.1.4

# The following section is specific to Flutter.
flutter:
  uses-material-design: true
  1. Copy and paste the following file into the 'test' directory:
workouts_bloc_test.dart
import 'package:bloc_test/bloc_test.dart';
import 'package:bug/failures.dart';
import 'package:bug/use_case.dart';
import 'package:bug/workout_entity.dart';
import 'package:bug/workouts_bloc.dart';
import 'package:bug/workouts_event.dart';
import 'package:bug/workouts_state.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockGetAllWorkouts extends Mock implements GetAllWorkouts {}

void main() {
  late MockGetAllWorkouts mockGetAllWorkouts;

  setUp(() {
    mockGetAllWorkouts = MockGetAllWorkouts();
  });

  /// Create a workout
  WorkoutEntity tWorkout = WorkoutEntity();

  group('GetAllWorkouts', () {
    List<WorkoutEntity> workouts = [tWorkout];

    blocTest<WorkoutsBloc, WorkoutsState>(
      'should emit [WorkoutsLoadFailure] when loading workouts fails',
      build: () {
        when(() async => mockGetAllWorkouts(NoParams()))
            .thenAnswer((_) async => Left(ReadFailure()));

        return WorkoutsBloc(
          getAllWorkouts: mockGetAllWorkouts,
        );
      },
      act: (bloc) => bloc.add(WorkoutsLoaded()),
      expect: () => [isA<WorkoutsLoadFailed>()],
    );

    blocTest<WorkoutsBloc, WorkoutsState>(
      'should emit [WorkoutsLoadSuccess] when loading workouts succeeds',
      build: () {
        when(() async => mockGetAllWorkouts(NoParams()))
            .thenAnswer((_) async => Right(workouts));

        return WorkoutsBloc(
          getAllWorkouts: mockGetAllWorkouts,
        );
      },
      act: (bloc) => bloc.add(WorkoutsLoaded()),
      expect: () => [isA<WorkoutsLoadSuccess>()],
    );
  });

  test(
    'GetAllWorkouts should emit [WorkoutsLoadFailure] when loading workouts fails',
    () async {
      // Arrange
      when(() async => mockGetAllWorkouts(NoParams()))
          .thenAnswer((_) async => Left(ReadFailure()));

      final bloc = WorkoutsBloc(
        getAllWorkouts: mockGetAllWorkouts,
      );

      // Assert later
      final expected = [
        WorkoutsLoadInProgress(),
        WorkoutsLoadFailed(message: ''),
      ];
      expectLater(bloc.stream, emitsInOrder(expected));

      // Act
      bloc.add(WorkoutsLoaded());
    },
  );
}
  1. Copy and paste the following files into the 'lib' directory:
failures.dart
import 'package:equatable/equatable.dart';

abstract class Failure extends Equatable {
  @override
  List<Object?> get props => [];
}

class ReadFailure extends Failure {}
i_workout_repository.dart
import 'package:dartz/dartz.dart';

import 'failures.dart';
import 'workout_entity.dart';

abstract class IWorkoutRepository {
  /// Gets all [WorkoutEntity] from the database.
  ///
  /// Returns [List<WorkoutEntity>].
  Future<Either<Failure, List<WorkoutEntity>>> getAllWorkoutEntities();
}
use_case.dart
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import 'failures.dart';
import 'i_workout_repository.dart';
import 'workout_entity.dart';

// Parameters have to be put into a container object so that they can be
// included in this abstract base class method definition.
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

class NoParams extends Equatable {
  @override
  List<Object> get props => [];
}

class GetAllWorkouts implements UseCase<List<WorkoutEntity>, NoParams> {
  final IWorkoutRepository repository;

  const GetAllWorkouts(this.repository);

  @override
  Future<Either<Failure, List<WorkoutEntity>>> call(NoParams params) async {
    return await repository.getAllWorkoutEntities();
  }
}
workout_entity.dart
import 'package:equatable/equatable.dart';

class WorkoutEntity extends Equatable {
  @override
  List<Object?> get props => [];
}
workouts_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';

import 'use_case.dart';
import 'workouts_event.dart';
import 'workouts_state.dart';

const String WORKOUT_LOAD_FAILURE_MESSAGE =
    'Workouts failed to load. Are there new fields present in [WorkoutEntity]?';

class WorkoutsBloc extends Bloc<WorkoutsEvent, WorkoutsState> {
  final GetAllWorkouts getAllWorkouts;

  WorkoutsBloc({
    required this.getAllWorkouts,
  }) : super(WorkoutsLoadInProgress());

  @override
  Stream<WorkoutsState> mapEventToState(
    WorkoutsEvent event,
  ) async* {
    if (event is WorkoutsLoaded) {
      yield* _mapWorkoutsLoadedToState();
    }
  }

  Stream<WorkoutsState> _mapWorkoutsLoadedToState() async* {
    final workoutsEither = await getAllWorkouts(NoParams());

    yield workoutsEither.fold(
      (failure) => WorkoutsLoadFailed(message: WORKOUT_LOAD_FAILURE_MESSAGE),
      (workouts) => WorkoutsLoadSuccess(workouts: workouts),
    );
  }
}
workouts_events.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

@immutable
abstract class WorkoutsEvent extends Equatable {
  const WorkoutsEvent();

  @override
  List<Object> get props => [];
}

/// Load all workouts.
class WorkoutsLoaded extends WorkoutsEvent {}

workouts_state.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import 'workout_entity.dart';

@immutable
abstract class WorkoutsState extends Equatable {
  const WorkoutsState();

  @override
  List<Object> get props => [];
}

class WorkoutsLoadInProgress extends WorkoutsState {}

class WorkoutsLoadSuccess extends WorkoutsState {
  final List<WorkoutEntity> workouts;

  const WorkoutsLoadSuccess({required this.workouts});

  @override
  List<Object> get props => [workouts];

  @override
  String toString() => 'WorkoutsLoaded { workouts: $workouts }';
}

class WorkoutsLoadFailed extends WorkoutsState {
  final String message;

  const WorkoutsLoadFailed({required this.message});

  @override
  List<Object> get props => [message];
}
  1. flutter test
  2. See errors

Expected Behavior
Expected: Mocked function correctly outputs.
Actual: Tests cannot proceed due to the type Null... error.

@jrmallorca
Copy link
Author

Removing async from the when part of the method fixed it. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant