тип «Null» не является подтипом типа «Future<Either<Failure, NumberTrivia>>»

Я учился делать тест-драйв (tdd), а также чистую архитектуру кода на флаттере, и снова и снова сталкивался с незнакомой проблемой. То есть тип 'Null' не является подтипом типа 'Future<Either<Failure, NumberTrivia>>'. Этот метод объявлен в абстрактном классе NumberTriviaRepository. NumberTrivia — это простой класс сущности, как показано ниже.

      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];
}

Репозиторий NumberTrivia

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

Вариант использования — GetConcreteNumberTrivia

      
class GetConcreteNumberTrivia {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

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

Вот основной тестовый файл

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

Я не знаю, где я делаю ошибку, но у меня проблемы с прохождением этого теста. Я считаю, что оба результата должны быть объектом NumberTrivia, но кажется, что один из них имеет значение null, и я не могу понять, почему это так.

Я ожидаю, что объект будет того же типа (NumberTrivia) в функции ожидания во время теста

3 ответа

Это может быть вызвано тем, что вы используете старую версию Mockito, которая не поддерживает нулевую безопасность. Попробуйте обновить свою версию до ^5.0.0.

Это часто доставляет неудобства, если учебники относятся к pre-null-safety или если некоторые пакеты с тех пор значительно изменились. Не знаю, был ли это этот или какой-то туториал, один из них вызывал у меня настоящую головную боль, потому что для работы в сегодняшней версии требовались значительные изменения.

Если вы используете версию до ^5.0.0.

Чтобы использовать сгенерированные фиктивные классы Mockito, добавьте зависимость build_runner в файле pubspec.yaml вашего пакета в разделе dev_dependencies; что-то вроде build_runner: ^1.11.0.

      flutter pub run build_runner build
# OR
dart run build_runner build

Получившийся класс выглядит следующим образом, и вы пройдете тест

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

Пример

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

Согласно ответу Сукси: у меня это сработало, но мне также нужно было выполнить 1) пункт:

  1. добавьте Mockito и build_runner в ОБА dev_dependents

Потом я сделал, как Сукси сказала:

  1. в моем файле с тестом я внес следующие изменения:

    импортировать «пакет: mockito/annotations.dart»;

    // класс MockNumberTriviaRepository расширяет Mock // реализует NumberTriviaRepository {}

    @GenerateNiceMocks([MockSpec()])

после этого у меня возникла ошибкаockNumberTriviaRepository undefined. Это нормально, игнорируйте это.

  1. затем запускаю из терминала (я использую AndroidStudio) команду: flutter pub run build_runner build (или dart вместо flutter)

  2. после этого в той же папке, что и тестовый файл, появляется сгенерированный файл, и я импортировал его в свой тестовый файл, чтобы исправить ошибку.

когда я запускаю тест, он пройден.

Другие вопросы по тегам