type 'Null' is not a subtype of type 'Future<Either<Failure, NumberTrivia>>'

887 views Asked by At

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

3

There are 3 answers

2
w461 On

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.

0
sooxie On

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);
    },
  );
}

0
SinBar Art On

According to sooxie answer: it worked for me, but I needed also to do the 1) point:

  1. add mockito and build_runner to the dev_dependencies BOTH

Than I did how sooxie said:

  1. in my file with test I did these changes:

    import 'package:mockito/annotations.dart';

    // class MockNumberTriviaRepository extends Mock // implements NumberTriviaRepository {}

    @GenerateNiceMocks([MockSpec()])

after that I had a error of mockNumberTriviaRepository undefined. It is ok, ignore that.

  1. then I run from the terminal (I use AndroidStudio) command: flutter pub run build_runner build (or dart instead flutter)

  2. after that there appears generated file in the same folder the test file is and I imported it to my test file to fix the error.

when I run my test, it passed.