тип «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) пункт:
- добавьте Mockito и build_runner в ОБА dev_dependents
Потом я сделал, как Сукси сказала:
в моем файле с тестом я внес следующие изменения:
импортировать «пакет: mockito/annotations.dart»;
// класс MockNumberTriviaRepository расширяет Mock // реализует NumberTriviaRepository {}
@GenerateNiceMocks([MockSpec()])
после этого у меня возникла ошибкаockNumberTriviaRepository undefined. Это нормально, игнорируйте это.
затем запускаю из терминала (я использую AndroidStudio) команду: flutter pub run build_runner build (или dart вместо flutter)
после этого в той же папке, что и тестовый файл, появляется сгенерированный файл, и я импортировал его в свой тестовый файл, чтобы исправить ошибку.
когда я запускаю тест, он пройден.