'Flutter Test: temporarily disable Firebase or somehow make the test not fail because of Firebase?

I wrote a test:

homeScreenWidgetsTest() {
  testWidgets('Home page screen widgets', (WidgetTester tester) async {
   
    await tester.pumpWidget(
      MaterialApp(
          home:Provider<AppDatabase>(
              create: (context) => AppDatabase(),
              child: HomePage(),
              dispose: (context, db) => db.close(),
            ),
      ),
    );

    await tester.pumpAndSettle(Duration(seconds: 15));
    expect(find.byType(AppBar), findsOneWidget);
  });
}

It throws this error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following FirebaseException was thrown running a test:
[core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()

When the exception was thrown, this was the stack:
#0      MethodChannelFirebase.app (package:firebase_core_platform_interface/src/method_channel/method_channel_firebase.dart:149:5)
#1      Firebase.app (package:firebase_core/src/firebase.dart:55:41)
#2      FirebaseCrashlytics.instance (package:firebase_crashlytics/src/firebase_crashlytics.dart:33:55)
#3      FirebaseLogging.log (package:.../logging/firebase_logging.dart:5:25)
#4      MyClass.work (package:.../oauth/oauth.dart:136:21)
<asynchronous suspension>

The error happens at the log line:

static Future<bool> work() async {
    FirebaseLogging.log("Start work.");

If I add the Firebase.initializeApp() in the main method of my test:

Future<void> main() async {
  await Firebase.initializeApp();
  homeScreenWidgetsTest();
}

It throws these errors:

package:flutter/src/services/platform_channel.dart 142:86                                       MethodChannel.binaryMessenger
package:flutter/src/services/platform_channel.dart 148:36                                       MethodChannel._invokeMethod
package:flutter/src/services/platform_channel.dart 331:12                                       MethodChannel.invokeMethod
package:flutter/src/services/platform_channel.dart 344:41                                       MethodChannel.invokeListMethod
package:firebase_core_platform_interface/src/method_channel/method_channel_firebase.dart 31:37  MethodChannelFirebase._initializeCore
package:firebase_core_platform_interface/src/method_channel/method_channel_firebase.dart 73:13  MethodChannelFirebase.initializeApp
package:firebase_core/src/firebase.dart 42:47                                                   Firebase.initializeApp
test\home_screen_test.dart 14:18                                                         main

Failed to load "E:\...\home_screen_test.dart": Null check operator used on a null value

So it throws error at this line: await Firebase.initializeApp();

How to fix this problem? Thanks in advance.

This is the FirebaseLogging.log():

class FirebaseLogging {
  static void log(String log) {
    FirebaseCrashlytics.instance.log(log);
  }
}


Solution 1:[1]

I was having the same issue when I updated the Firebase package.

I created a class to swap FirebaseAnalytics with a mocked version based on the environment. Then replaced FirebaseAnalytics with the wrapper class AnalyticsService.

This is so hacky, and I hate it.

If anyone has a better way, please share.

import 'dart:io';

import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_core/firebase_core.dart';

// The is class is needed because firebase is horrible and apparently hates developers
// - also calling `FirebaseAnalytics.instance` causes tests to fail
class AnalyticsService {
  static get instance => Platform.environment.containsKey('FLUTTER_TEST') ? FakeFirebaseAnalytics() : FirebaseAnalytics.instance;
}

class FakeFirebaseAnalytics implements FirebaseAnalytics {
  @override
  FirebaseApp app;

  @override
  FirebaseAnalyticsAndroid get android => null;

  @override
  Future<void> logAdImpression({String adPlatform, String adSource, String adFormat, String adUnitName, double value, String currency, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logAddPaymentInfo({String coupon, String currency, String paymentType, double value, List<AnalyticsEventItem> items, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logAddShippingInfo({String coupon, String currency, double value, String shippingTier, List<AnalyticsEventItem> items, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logAddToCart({List<AnalyticsEventItem> items, double value, String currency, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logAddToWishlist({List<AnalyticsEventItem> items, double value, String currency, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logAppOpen({AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logBeginCheckout({double value, String currency, List<AnalyticsEventItem> items, String coupon, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logCampaignDetails({String source, String medium, String campaign, String term, String content, String aclid, String cp1, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logEarnVirtualCurrency({String virtualCurrencyName, num value, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logEcommercePurchase(
      {String currency,
      double value,
      String transactionId,
      double tax,
      double shipping,
      String coupon,
      String location,
      int numberOfNights,
      int numberOfRooms,
      int numberOfPassengers,
      String origin,
      String destination,
      String startDate,
      String endDate,
      String travelClass}) async {}

  @override
  Future<void> logEvent({String name, Map<String, Object> parameters, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logGenerateLead({String currency, double value, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logJoinGroup({String groupId, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logLevelEnd({String levelName, int success, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logLevelStart({String levelName, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logLevelUp({int level, String character, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logLogin({String loginMethod, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logPostScore({int score, int level, String character, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logPresentOffer({String itemId, String itemName, String itemCategory, int quantity, double price, double value, String currency, String itemLocationId}) async {}

  @override
  Future<void> logPurchase(
      {String currency, String coupon, double value, List<AnalyticsEventItem> items, double tax, double shipping, String transactionId, String affiliation, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logPurchaseRefund({String currency, double value, String transactionId}) async {}

  @override
  Future<void> logRefund({String currency, String coupon, double value, double tax, double shipping, String transactionId, String affiliation, List<AnalyticsEventItem> items}) async {}

  @override
  Future<void> logRemoveFromCart({String currency, double value, List<AnalyticsEventItem> items, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logScreenView({String screenClass, String screenName, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logSearch(
      {String searchTerm,
      int numberOfNights,
      int numberOfRooms,
      int numberOfPassengers,
      String origin,
      String destination,
      String startDate,
      String endDate,
      String travelClass,
      AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logSelectContent({String contentType, String itemId}) async {}

  @override
  Future<void> logSelectItem({String itemListId, String itemListName, List<AnalyticsEventItem> items, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logSelectPromotion(
      {String creativeName, String creativeSlot, List<AnalyticsEventItem> items, String locationId, String promotionId, String promotionName, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logSetCheckoutOption({int checkoutStep, String checkoutOption}) async {}

  @override
  Future<void> logShare({String contentType, String itemId, String method}) async {}

  @override
  Future<void> logSignUp({String signUpMethod}) async {}

  @override
  Future<void> logSpendVirtualCurrency({String itemName, String virtualCurrencyName, num value}) async {}

  @override
  Future<void> logTutorialBegin() async {}

  @override
  Future<void> logTutorialComplete() async {}

  @override
  Future<void> logUnlockAchievement({String id}) async {}

  @override
  Future<void> logViewCart({String currency, double value, List<AnalyticsEventItem> items, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> logViewItem({String currency, double value, List<AnalyticsEventItem> items}) async {}

  @override
  Future<void> logViewItemList({List<AnalyticsEventItem> items, String itemListId, String itemListName}) async {}

  @override
  Future<void> logViewPromotion({String creativeName, String creativeSlot, List<AnalyticsEventItem> items, String locationId, String promotionId, String promotionName}) async {}

  @override
  Future<void> logViewSearchResults({String searchTerm}) async {}

  @override
  Future<void> resetAnalyticsData() async {}

  @override
  Future<void> setAnalyticsCollectionEnabled(bool enabled) async {}

  @override
  Future<void> setConsent({bool adStorageConsentGranted, bool analyticsStorageConsentGranted}) async {}

  @override
  Future<void> setCurrentScreen({String screenName, String screenClassOverride = 'Flutter', AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> setDefaultEventParameters(Map<String, Object> defaultParameters) async {}

  @override
  Future<void> setSessionTimeoutDuration(Duration timeout) async {}

  @override
  Future<void> setUserId({String id, AnalyticsCallOptions callOptions}) async {}

  @override
  Future<void> setUserProperty({String name, String value, AnalyticsCallOptions callOptions}) async {}

  @override
  Map get pluginConstants => {};
}

To apply this hack to the code in the question:

class FirebaseLogging {
  static void log(String log) {
    if (Platform.environment.containsKey('FLUTTER_TEST')) return; 
    FirebaseCrashlytics.instance.log(log);
  }
}

Solution 2:[2]

You can mock channels like it's done in official flutter fire. Call initFirebaseForTest will create a fake firebase instance and so firebase analytics won't bother you anymore.

Future<FirebaseApp> initFirebaseForTest([int? counter]) async {
  try {
    setupFirebaseAuthMocks();
    return await Firebase.initializeApp(
      name: '$counter ?? 0AB',
      options: const FirebaseOptions(
        apiKey: '',
        appId: '',
        messagingSenderId: '',
        projectId: '',
      ),
    );
  } catch (_) {}
  return Firebase.app();
}

void setupFirebaseAuthMocks([Callback? customHandlers]) {
  TestWidgetsFlutterBinding.ensureInitialized();

  MethodChannelFirebase.channel.setMockMethodCallHandler((call) async {
    if (call.method == 'Firebase#initializeCore') {
      return [
        {
          'name': defaultFirebaseAppName,
          'options': {
            'apiKey': '123',
            'appId': '123',
            'messagingSenderId': '123',
            'projectId': '123',
          },
          'pluginConstants': {},
        }
      ];
    }

    if (call.method == 'Firebase#initializeApp') {
      return {
        'name': call.arguments['appName'],
        'options': call.arguments['options'],
        'pluginConstants': {},
      };
    }

    if (customHandlers != null) {
      customHandlers(call);
    }

    return null;
  });
}

Future<T> neverEndingFuture<T>() async {
  // ignore: literal_only_boolean_expressions
  while (true) {
    await Future.delayed(const Duration(minutes: 5));
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 mcfly