skip to Main Content

I was learning how to do test drive development (tdd) as well as clean code architecture on flutter and I kept getting into unfamiliar problem again and again. That is – type ‘Null’ is not a subtype of type ‘Future<Either<Failure, NumberTrivia>>’. This method is declared in an abstract class NumberTriviaRepository
The NumberTrivia is a simple entity class as follow

class NumberTrivia extends Equatable {
  final int number;
  final String text;
  const NumberTrivia({
    required this.number,
    required this.text,
  });

  @override
  // TODO: implement props
  List<Object?> get props => [number, text];
}

The NumberTriviaRepository


abstract class NumberTriviaRepository {
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}

The usecase – GetConcreteNumberTrivia


class GetConcreteNumberTrivia {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

  Future<Either<Failure, NumberTrivia>> execute({required number}) async {
    return await repository.getConcreteNumberTrivia(number);
  }
}

Here is the main test file


class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  late MockNumberTriviaRepository mockNumberTriviaRepository;
  late GetConcreteNumberTrivia usecase;

  setUp(() {
    mockNumberTriviaRepository = MockNumberTriviaRepository();
    usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
  });

  final testNumber = 1;
  const testNumberTrivia = NumberTrivia(number: 1, text: 'test');

  test('should get trivia for the number from the repository', () async {
    when(mockNumberTriviaRepository.getConcreteNumberTrivia(testNumber))
        .thenAnswer((_) async => const Right(testNumberTrivia));

    final result = await usecase.execute(number: testNumber);
    log('Result equals ${result}');
    expect(result, equals(const Right(testNumberTrivia)));
    verify(mockNumberTriviaRepository.getConcreteNumberTrivia(testNumber));
    verifyNoMoreInteractions(mockNumberTriviaRepository);
  });
}

I don’t know where I’m making a mistake but I’m having trouble passing this test case. I believe both the results should be NumberTrivia object but it seems like one of them is null and I can’t figure why that is the case.

I expect the object to be of the same type (NumberTrivia) in the expect function during the test

2

Answers


  1. This might be caused if you are using an old Mockito version that does not support null-safety. Try upgrading your version to ^5.0.0.

    This is frequently a pain if tutorials are from pre-null-safety or if some packages have significantly changed since then. Not sure if it was this or some bloc tutorial, one was causing me real headaches because it required significant changes to run in today’s version.

    Login or Signup to reply.
  2. If you use version to ^5.0.0.

    To use Mockito’s generated mock classes, add a build_runner dependency in your package’s pubspec.yaml file, under dev_dependencies; something like build_runner: ^1.11.0.

    flutter pub run build_runner build
    # OR
    dart run build_runner build
    

    The resulting class is as follows, and you will pass the test

    • get_concrete_number_trivia_test.mocks.dart
    // Mocks generated by Mockito 5.3.2 from annotations
    // in flutter_go/test/features/number_trivia/domain/usecases/get_concrete_number_trivia_test.dart.
    // Do not manually edit this file.
    
    // ignore_for_file: no_leading_underscores_for_library_prefixes
    import 'dart:async' as _i4;
    
    import 'package:dartz/dartz.dart' as _i2;
    import 'package:flutter_go/core/error/failures.dart' as _i5;
    import 'package:flutter_go/features/number_trivia/domain/entities/number_trivia.dart'
        as _i6;
    import 'package:flutter_go/features/number_trivia/domain/repositories/number_trivia_repository.dart'
        as _i3;
    import 'package:mockito/mockito.dart' as _i1;
    
    // ignore_for_file: type=lint
    // ignore_for_file: avoid_redundant_argument_values
    // ignore_for_file: avoid_setters_without_getters
    // ignore_for_file: comment_references
    // ignore_for_file: implementation_imports
    // ignore_for_file: invalid_use_of_visible_for_testing_member
    // ignore_for_file: prefer_const_constructors
    // ignore_for_file: unnecessary_parenthesis
    // ignore_for_file: camel_case_types
    // ignore_for_file: subtype_of_sealed_class
    
    class _FakeEither_0<L, R> extends _i1.SmartFake implements _i2.Either<L, R> {
      _FakeEither_0(
        Object parent,
        Invocation parentInvocation,
      ) : super(
              parent,
              parentInvocation,
            );
    }
    
    /// A class which mocks [NumberTriviaRepository].
    ///
    /// See the documentation for Mockito's code generation for more information.
    class MockNumberTriviaRepository extends _i1.Mock
        implements _i3.NumberTriviaRepository {
      @override
      _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>> getConcreteNumberTrivia(
              int? number) =>
          (super.noSuchMethod(
            Invocation.method(
              #getConcreteNumberTrivia,
              [number],
            ),
            returnValue:
                _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
                    _FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
              this,
              Invocation.method(
                #getConcreteNumberTrivia,
                [number],
              ),
            )),
            returnValueForMissingStub:
                _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
                    _FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
              this,
              Invocation.method(
                #getConcreteNumberTrivia,
                [number],
              ),
            )),
          ) as _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>);
      @override
      _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>
          getRandomNumberTrivia() => (super.noSuchMethod(
                Invocation.method(
                  #getRandomNumberTrivia,
                  [],
                ),
                returnValue:
                    _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
                        _FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
                  this,
                  Invocation.method(
                    #getRandomNumberTrivia,
                    [],
                  ),
                )),
                returnValueForMissingStub:
                    _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>.value(
                        _FakeEither_0<_i5.Failure, _i6.NumberTrivia>(
                  this,
                  Invocation.method(
                    #getRandomNumberTrivia,
                    [],
                  ),
                )),
              ) as _i4.Future<_i2.Either<_i5.Failure, _i6.NumberTrivia>>);
    }
    
    

    Example

    import 'package:dartz/dartz.dart';
    import 'package:flutter_go/features/number_trivia/domain/entities/number_trivia.dart';
    import 'package:flutter_go/features/number_trivia/domain/repositories/number_trivia_repository.dart';
    import 'package:flutter_go/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mockito/annotations.dart';
    import 'package:mockito/mockito.dart';
    
    // class MockNumberTriviaRepository extends Mock
    //     implements NumberTriviaRepository {}
    
    @GenerateNiceMocks([MockSpec<NumberTriviaRepository>()])
    
    // import generated mock classes
    import './get_concrete_number_trivia_test.mocks.dart';
    
    void main() {
      late GetConcreteNumberTrivia usecase;
      late MockNumberTriviaRepository mockNumberTriviaRepository;
    
      setUp(() {
        mockNumberTriviaRepository = MockNumberTriviaRepository();
        usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
      });
    
      final tNumber = 1;
      final tNumberTrivia = NumberTrivia(number: 1, text: 'test');
    
      test(
        'should get trivia for the number from the repository',
        () async {
          // "On the fly" implementation of the Repository using the Mockito package.
          // When getConcreteNumberTrivia is called with any argument, always answer with
          // the Right "side" of Either containing a test NumberTrivia object.
          when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
              .thenAnswer((_) async => Right(tNumberTrivia));
          // The "act" phase of the test. Call the not-yet-existent method.
          final result = await usecase.execute(number: tNumber);
          // UseCase should simply return whatever was returned from the Repository
          expect(result, Right(tNumberTrivia));
          // Verify that the method has been called on the Repository
          verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
          // Only the above method should be called and nothing more.
          verifyNoMoreInteractions(mockNumberTriviaRepository);
        },
      );
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search