Can't create unit test with FireStore for flutterfire, throws during init, even with simplest possible test - firebase

// ignore_for_file: avoid_print
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('core tests - coffee', () {
test('fetch coffees', () async {
await Firebase.initializeApp();
final FirebaseFirestore firestore = FirebaseFirestore.instance;
print('initialized');
expect(firestore, isNotNull);
});
});
}
This is in a test. Oddly, it works in the live app, but not in a simple test case.
Errors during Firebase.initializeApp() with:
Null check operator used on a null value
MethodChannel.binaryMessenger
package:flutter/…/services/platform_channel.dart:121
I don't want a testWidgets. I don't want a mocked FireStore. I want to hit the real database.
I'm on a mac, running a simulator, using VSC, although the simulator shouldn't even be involved.

Firebase plugins wraps around native platform (firebase) SDKs and i think they are not available on when running tests.
I would suggest to use either the emulator suite or fake_cloud_firestore package, or do manual testing by humans (connected with a different firebase instance)

Related

How to check if Firebase Function has already been initialized

Just in case its important, this is how I am importing the functions stuff
import * as functions from "firebase-functions";
import admin = require("firebase-admin");
I am currently using the below code to check if the firebase function has already been initialized:
if (admin.apps.length === 0) {
admin.initializeApp();
} else {
functions.logger.log("App already initialized");
}
I got this code from: How to check if a Firebase App is already initialized on Android
My problem is that this doesn't seem to be checking if the app has already been initialized because I see the log from the else statement but immediately after I see the error log that the default Firebase app does not exist.
If I call admin.initializeApp(); it works, but only sometimes, other times it would tell me that
"Unhandled error FirebaseAppError: The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once"
What is the correct way to see if the app has already been initialized?
I am not sure if the below is important but here goes:
I have my functions in separate files, to import the files into the index.ts file I:
Import the function:
import checkPayment = require("./checkPayment");
I'm not sure how to define what this is:
exports.checkPayment = checkPayment;
Perhaps I am importing the other functions incorrectly.
Thank you for your time.

Is it possible to assert the number of reads/writes in a Firebase Cloud Function test?

I have a test suite that runs integration tests for my Firebase Cloud Functions against a locally running Firebase emulator, is it possible to assert the number of reads/writes made to the emulated Firestore instance?
E.g. for the following function I would want to assert that 1 write occurs & 0 reads
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.addMessage = functions.https.onCall(async (data, context) => {
const original = data.query.text;
const snapshot = await admin.firestore().collection("/messages").add({ original: original });
return { code: 200, message: "Success" };
});
Currently, Its not possible to assert the number of reads and writes in a Firebase Cloud Function test.
I would suggest that the best way would be is to write the test data to the Firebase Emulator, then read it back and also count the number of collections/documents as well using Client SDK.
If you want to assert data coming from the Firebase Emulator, then you can use Requests Monitor. We used Client SDK for this purpose as Admin SDK requests and access calls will not be listed because it bypass Security Rules. It's a current limitation according to this Blog.
The Firebase emulator has a log that shows requests as shown in example below.
Note: I don't think that you should have a dependency on it, since Firebase Emulator Request Monitor is a new feature that its implementation may change and Admin SDK can be included over time.

How to test a Flutter application using Firebase?

I am using Flutter and Firebase as my database. I want to do some unit test of my application, but when I start this test :
testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
// Create the widget by telling the tester to build it.
await tester.pumpWidget(LoginPage());
expect(true, true);
});
I have the Exception :
No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()
But Firebase.initializeApp() is called in my main.dart here :
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
And I don't know how to initialize the Firebase app in my test.
Posting this as a community wiki as it's based on #GuilhermeGabanelli's comment.
If you check this flutterfire documentation on how to perform unit tests with Firebase Services you will see that:
The Firebase libraries need to run on an actual device or emulator. So if you want to run unit tests, you'll have to use Fakes instead. A Fake is a library that implements the API of a given Firebase library and simulates its behavior.
...
When initializing your app, instead of passing the actual instance of a Firebase library (e.g. FirebaseFirestore.instance if using Firestore), you pass an instance of a fake (e.g. FakeFirebaseFirestore()). Then the rest of your application will run as if it were talking to Firebase.
Followed by an example with sample code. I believe this is the cause of the issue you are facing and if you change your code to use Fakes this should be fixed.

How do the rest of the modules access "Firebase" class, after we initialize using ```Firebase.initializeApp();``` in the entry point?

So my app is working, but I want to understand what does the initialization actually do?
and why do we have access to a "Firebase" class only after we initialize?
in other words:
How do the rest of the modules access "Firebase" class, after we initialize using Firebase.initializeApp(); in the entry point?
for reference, this is one approach of how we initialize:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp()
);
}
My question is, how is the returned value of the initialized app stored? so that I can access it from any module?
In your case (no application name provided), it initializes a new FirebaseAppPlatform for the default Firebase App and returns it as a Future<FirebaseAppPlatform>. This method should be called before any usage of FlutterFire plugins.
Thanks to that FirebaseApp instance, all the Firebase module can get access to the initialized Firebase Application. Example for Firestore: FirebaseFirestore.instance
Thanks to that initialization using await, you'll also get a synchronous getter for the currentUser.

is it ok to call firebase.initializeApp() in the main [duplicate]

This question already has answers here:
No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp() in Flutter and Firebase
(27 answers)
Closed 2 years ago.
flutterfire recently introduced some changes and one of those is the need to call initializeApp() before using any plugin.
is it ok to call it in the main ? something like this :
void main() async {
await Firebase.initializeApp();
return runApp(App());
}
or maybe like this without async await
void main() {
Firebase.initializeApp();
return runApp(App());
}
i think the first example is more correct but its blocking the execution of the main and i have no idea how much time before the future complete?
The only requirement from Firebase is that you call Firebase.initializeApp() before any other Firebase APIs are called. If that condition isn't met, it will raise an exception explicitly telling you so.
In my code I call it in my main, which is the earliest place I can think if, and it works without problems:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
...
The initialization of Firebase at this level is pretty much instantaneous, as it's just waiting for the call to the native code to complete, and the native code itself does nothing more then look up the configuration values.
But if that is taking too long for you, you can call it without await. It just means that you may have to deal with that Future<FirebaseApp> later in your code where you access Firebase, typically by wrapping that in a FutureBuilder.

Resources