I just started with TDD and Clean Architecture in Flutter following a series at https://resocoder.com/2019/08/29/flutter-tdd-clean-architecture-course-2-entities-use-cases/.
At this stage there is no implementation of the Repository yet, just the abstract class which gets mocked for testing purposes and a use case. Now if I got this reverse dependency concept right, the repository gets injected into the use case class and the use case method will call correct repository method and return its result as in
abstract class NumberTriviaRepository {
// here we define the contracts, the methods that the implementation of this class
// (in the repository of the data layer ) must use.
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTriva();
}
class GetConcreteNumberTrivia {
final NumberTriviaRepository repository;
const GetConcreteNumberTrivia({required this.repository}) : super();
Future<Either<Failure, NumberTrivia>> execute({required int number}) async {
return await repository.getConcreteNumberTrivia(number);
}
}
Then in the test Mockito
is used:
when
and thenAnswer
.// create a mock repository class that implements the repository for the test
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
void main() {
// define a variable for the mock repository class
late MockNumberTriviaRepository mockNumberTriviaRepository;
// define a variable for the use case with the mock repository class
late GetConcreteNumberTrivia usecase;
setUp(() {
// initialise the variable instantiating the mock repository class
mockNumberTriviaRepository = MockNumberTriviaRepository();
// initialise the variable instantiating the use case injecting the mock repository class
usecase = GetConcreteNumberTrivia(repository: mockNumberTriviaRepository);
});
// test value
const tNumber = 1;
// mocked Entity
const tNumberTrivia = NumberTrivia(text: 'test', number: tNumber);
test('should get trivia for the number form the repository', () async {
// arrange
when(
// call the repository method to be tested from the mocked repository
mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber))
// force the mocked repository class method to return a specific value,
// in this case the Right side of the method's Either return type with the mocked Entity
.thenAnswer((_) async => const Right(tNumberTrivia));
// act
// get an Entity from the use case
final result = await usecase.execute(number: tNumber);
// assert
// compare the two Entities
expect(result, const Right(tNumberTrivia));
// check that repository method has been called with the test value
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
// chech that no other repository methods were called
verifyNoMoreInteractions(mockNumberTriviaRepository);
});
}
When I run the test I get the error message:
type 'Null' is not a subtype of type 'Future<Either<Failure, NumberTrivia>>'
test/features/number_trivia/domain/usecases/get_concrete_number_trivia_test.dart 11:7 MockNumberTriviaRepository.getConcreteNumberTrivia
test/features/number_trivia/domain/usecases/get_concrete_number_trivia_test.dart 38:40 main.<fn>
I tried commenting out the assert part of the test and the call of the use case, but the I still get the error, so I guess it comes from the "forced return of the mocked repository in .thenAnswer((_) async => const Right(tNumberTrivia))
.
Any Idea why the mock Entity is not returned?
Many thanks
Ok.. found the problem.. I rushed and I haven't really thought that the series I'm following is a bit outdated and meanwhile we got null-safety, which scrambled a few eggs.. so as stated in https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md the method for mocking classes is changed..
Old way
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRepository {}
New way (I went with the automatic way, but there is also a manual way)
build_runner
as dev dependency.import 'package:mockito/annotations.dart';
@GenerateMocks([NumberTriviaRepository])
Run dart run build_runner build
which will generate a <usecase file name>.mock.dart
file in the same location of the usecase file which will contain the MockRepository class to be used in the test (MockNumberTriviaRepository I my case).
Import that file
import 'get_concrete_number_trivia_test.mocks.dart';
Now the test can use the proper Mocked repository class.
Still is unclear to me why it's creating a <usecase file name>.mock.dart
file in the same location of the usecase file, and not a <repository file name>.mock.dart
file in the same location of the repository file. After all is repository mocked class that it's generating.
This leaves me wandering.. what should be done with the other tests for rest of the use cases for that repository? I guess just import the same <usecase file name>.mock.dart
file.
Solution 2
just switch from Mockito
to Mocktail
which does work as the old Mockito without any code generation.. Thanx Felix Angel once again.
You'll just need to add () =>
to both stubbing and verifying calls.
from
when(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber))
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
to
when(() => mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber))
verify(() => mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));