ResoCoder / finished-flutter-firebase-ddd-course

https://resocoder.com/flutter-firebase-ddd-course
GNU General Public License v3.0
396 stars 114 forks source link

The mystery of Unit Testing with Firestore #17

Open chstrong opened 4 years ago

chstrong commented 4 years ago

Dear Resocoder,

Your tutorial is absolutely fantastic.

However, one mystery remains, and this is unit testing the NoteRepository class and the NoteWatcherBloc, and NoteActorBloc class with Firestore.

This is therefore a "feature" request.

Your tutorial is great but can get very complex for amateur programmers. So I'm trying a test-driven approach for your domain driven tutorial, in order to solve each concern separately. I'm starting therefore with the domain and infrastructure / repository layer.

Mockito doesn't seem to be able to accept constructors and hence I can't pass a Mock instance of Firebase to the repository, or the Bloc. At the moment I'm thinking of writing an additional method in the NoteRepository which I can use to parse the Firebase Mock instance, but I'm sure you will scream at me and tell me it's not good code practice :) And the problem with the Bloc still remains.

I think a separate video would be helpful that explains how to test repository and bloc classes with Firestore.

There is nobody, that is showing this, so it seems to be a complex topic that nobody can copy, as there are no examples :).

Or all use AWS Lambda with JSON and nobody uses Firebase :)

All are showing FirebaseAuth, but nobody shows how to test repository classes and blocs. I didn't figure out how to do it yet but am working on it.

manuelvargastapia commented 3 years ago

Hi @chstrong. I also struggled a lot with testing Firestore and finally, I used the cloud_firestore_mocks package. It's still tricky but did the trick. You can check my code for examples.

To test a bloc, primary you'll need the bloc_test package. With it, it's pretty easy to implement unit testing in blocs. Again, you could check my code for examples.

manjavacasjaime commented 3 years ago

THIS WORKS FOR ME. I've followed ResoCoder's tutorial step by step.

I was having a lot of trouble trying to test widgets that use BloCs, whose repositories communicate with Firebase. Hope this makes things easier.

1. These are my imports and the annotations used to auto-generate the mocks for my IBlaBlaRepository: Note: casedashboard_widget_test.mocks.dart is an auto-generated file. In order to generate yours, add mockito's GenerateMocks annotation and run "flutter pub run build_runner build --delete-conflicting-outputs".

import 'package:bethemis_abogados/application/chat/message_watcher/message_watcher_bloc.dart'; import 'package:bethemis_abogados/application/expense/expense_watcher_bloc.dart'; import 'package:bethemis_abogados/domain/chat/i_message_repository.dart'; import 'package:bethemis_abogados/domain/expense/i_expense_repository.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart';

import 'casedashboard_widget_test.mocks.dart';

@GenerateMocks([ IExpenseRepository ], customMocks: [ MockSpec<,IMessageRepository>(returnNullOnMissingStub: true), ]) void main() {

2. This is the "stream" variable that I want to be returned by one of my Repository methods. Instead of calling Firebase, the Repository method will return this variable.

var messageAux = Message( messageID: UniqueId(), date: DateTime.utc(1989, 11, 9), from: UniqueId(), text: 'text', ); var list = listOf(messageAux); final stream = Stream<Either<MessageFailure, KtList<,Message>>>.fromIterable([right(list)]);

3. And, finally, here's a look at the testWidgets method. Note: the BloCs are not being mocked. I'm just creating them with the IBlaBlaRepository auto-generated mocks. Note: I use mockito's when( ) method to return the "stream" that I've mentioned before. Note: make sure that the BlocProviders you're using are outside the widget you're testing.

testWidgets( 'Dashboard shows desired reduxWidgets when the selected case is not null', (WidgetTester tester) async {

  IMessageRepository messageRepository = MockIMessageRepository();
  when(messageRepository.watchAllMessages(caseAux, 20)).thenAnswer((_) => stream);

  MessageWatcherBloc messageWatcherBloc = MessageWatcherBloc(messageRepository);
  IExpenseRepository expenseRepository = MockIExpenseRepository();
  ExpenseWatcherBloc expenseWatcherBloc = ExpenseWatcherBloc(expenseRepository);

  var widget = MaterialApp(
    home: MultiBlocProvider(
      providers: [
        BlocProvider<MessageWatcherBloc>.value(value: messageWatcherBloc),
        BlocProvider<ExpenseWatcherBloc>.value(value: expenseWatcherBloc),
      ],
      child: CaseDashboard(myCase: caseAux),
    ),
  );

  await tester.pumpWidget(widget);

  final seleccioneTextFinder = find.text(seleccioneUnCasoText);
  final reduxWidgetFinder = find.byKey(const Key('reduxWidget'));

  expect(seleccioneTextFinder, findsNothing);
  expect(reduxWidgetFinder, findsNWidgets(5));
},

);