Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing On Flutter Firebase functions

Good day, I am trying to perform some unit testing on the below function that creates a document in Cloud Firestore. I have used a function in my app and it creates a document, but I want to write a test.dart file that performs unit testing for the below function and prints some output even on the console for verifications.

I think I am not writing my Test.dart in the proper way. I get an error.

Function in file createdatabase.dart

Future<dynamic> createDoc(dataMap,collection) async {
  final TransactionHandler createTransaction = (Transaction tx) async {
    final DocumentSnapshot ds = await tx.get(db.collection(collection).document());
    final Map<String, dynamic> result = {};
    result.addAll(dataMap);
    result['id'] = ds.documentID;
    await tx.set(ds.reference, result);

    return result;
  };

Test.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:test/test.dart';
import '../lib/service/createfirebase.dart';

void main() {
  CreateFirebase cf = new CreateFirebase();   
   //test    
    test('Creating doc on firestore ', () async{ 
      Object dataObj ={'name':'Dev','title':'Dev'};
      var create = await cf.createDoc(dataObj, 'crude');
      expect(true,create);
      print('The doc details are');
      print(dataObj);
    });

}

The error after running this test is MissingPluginException(No implementation found for method Firestore#runTransaction on channel plugins.flutter.io/cloud_firestore)

But I do not understand why since I have all the dependencies and if I call that function in another class the doc gets created. But calling inside this test gives the above error. I guess I am not doing it in the right way.

Any contribution or any reference I can look at that can help on testing such functions?

like image 514
Frank Avatar asked Nov 09 '18 12:11

Frank


1 Answers

TLDR: mazei513's comment is right. As Flutter Firestore is a plugin and not a package, it needs a platform to run on. This platform could be an iOS phone or an Android virtual device for instance. Therefore, you can't write a unit test to test a Firestore command. You have to use the integration_test package to execute this kind of tests.

Understanding the difference between plugin vs package

As stated by the doc, a plugin

is a special kind of package that makes platform functionality available to the app. Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, Linux, or any combination thereof.

If you take a look at the Firestore plugin in /usr/local/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.7.0, you'll see:

jjanvier:nouba$ ls -l /usr/local/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.7.0                                   
total 48
drwxr-xr-x 4 jjanvier jjanvier  4096 Oct  6 17:20 android
drwxr-xr-x 3 jjanvier jjanvier  4096 Oct  6 17:20 ios
drwxr-xr-x 3 jjanvier jjanvier  4096 Oct  6 17:20 lib
drwxr-xr-x 3 jjanvier jjanvier  4096 Oct  6 17:20 macos
-rw-r--r-- 1 jjanvier jjanvier   912 Oct  6 17:20 pubspec.yaml

As Firebase plugins actually run native code depending on the platform (Android, iOS, MacOS...), they need a "real" device to run on. Therefore, it doesn't work with flutter test that is nothing but a simple Dart executor.

Testing a Firestore repository (or a command or a query)

About the device emulator

To get a platform your tests can run on, you have several options. For instance:

  • boot an Android Virtual Device from your IDE. That's what I do when I work locally.
  • setup an Android Virtual Device on your continuous integration pipeline. For instance you this one for Github actions. Then you can start the emulator from the command line.
  • use Firebase Test Lab. I didn't use it, but it should work.

Using integration_test

Follow this documentation to configure integration_test for your project. Don't forget to use testWidgets instead of test as this what will allow to launch your tests in the device emulator instead of in the Dart simple environment.

Once done, all the integration/end to end tests can be launched with flutter test integration_test.

Do not mess your production database

If you launch the command flutter test integration_test as is, there is a high chance the document {'name':'Dev','title':'Dev'} present in your test will appear in your production database.

To counteract this problem, you have mainly 2 options at your disposal:

  1. setup a real Firebase project that you'll use for your tests
  2. use the Firebase local emulator suite

For my project, I chose the second option. With a single command, you can boot the Firebase emulator, loads fixtures and run the tests :

firebase --config="firebase.test.json" emulators:exec --import fixtures/ "flutter test integration_test/ --dart-define=FOO=bar"

Let's detail the command:

  • --config="firebase.test.json" launch firebase with this config file. This allows me to keep my local emulator suite running while I launch the tests
  • --import fixtures/: loads the folder "fixtures" as dataset
  • emulators:exec "flutter test integration_test/ --dart-define=FOO=bar": launch the integration tests with a FOO env var

About mocking/faking Firestore

I didn't suggest you to mock or fake Firestore as it makes no sense in your case. There is no point in using a double for Firestore if what you want to test is that you can actually write properly in Firestore.

But, in some cases it can be useful. For instance to unit test a business behavior that at some point writes or queries Firestore.

Again, you have several solutions:

  • you can mock Firestore by yourself, like it's done here. But it's quite complex and painful to do IMO.
  • you can use the package fake_cloud_firestore. As the name suggest, it's a complete Cloud Firestore fake. So far, so good, it works perfectly for me.
like image 115
jjanvier Avatar answered Nov 09 '22 16:11

jjanvier