So I have an web app written with flutter, and recently I added security rules on Firestore DB, but I cannot find any documentation on how to test these security rules with flutter test.
I also want to do this test against the Firebase Emulator, which I setup.
I found lots of examples to test it with node and npm, like HERE, but I don't use JS, I wrote everything within Flutter.
I have tried different scenarios which mostly failed:
Mocking Firebase.initializeApp() with this solution but since it's mocked I cannot use any implementation for FirebaseFirestore.instance or FirebaseAuth.instance.
Sample code:
Future<void> main() async {
setupFirebaseAuthMocks();
await Firebase.initializeApp();
final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
firebaseFirestore.useFirestoreEmulator(
EMULATOR_HOST,
FIREBASE_PORT,
sslEnabled: false,
);
final CollectionReference colUserRef =
firebaseFirestore.collection('users');
BaseUser user = BaseUser(
uid: 'uid',
email: 'email',
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
);
test('Check if', () async {
await firebaseFirestore.collection('users').doc().set(user.toJson());
print('works?');
});
}
Error I got:
package:flutter/src/services/platform_channel.dart 294:7 MethodChannel._invokeMethod
MissingPluginException(No implementation found for method DocumentReference#set on channel plugins.flutter.io/firebase_firestore)
I tried to use FakeFirebaseFirestore() but this class doesn't have useFirestoreEmulator()
Sample code:
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
...
FirestoreService firestoreService = FirestoreService.mock(firestoreInstance: FakeFirebaseFirestore());
firestoreService.firebaseFirestore.useFirestoreEmulator('127.0.0.1', 8081);
...
Error I got:
Class 'FakeFirebaseFirestore' has no instance method 'useFirestoreEmulator' with matching arguments.
Receiver: Instance of 'FakeFirebaseFirestore'
Solution that I found to finally work!
I just created a fakeApp() which I use with the integration tests that Flutter Web has HERE. Since I have to initialize Firebase and my requirement is to use Firebase Emulator within a pipeline.
Sample Code:
// Simple and bare bone flutter app
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(context) => const Center(
child: Text('Firebase Security Rules Test!'));
}
Future<void> fakeApp() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instanceFor(app: Firebase.app());
runApp(const MyApp());
}
// Tests implementation
Future<void> main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
late FirebaseFirestore firebaseFirestore;
late FirebaseAuth firebaseAuth;
late CollectionReference colUserRef;
late BaseUser user;
setUpAll(() async {
await fakeApp();
firebaseFirestore = FirebaseFirestore.instance;
firebaseAuth = FirebaseAuth.instance;
firebaseAuth.useAuthEmulator(
EMULATOR_HOST,
FIREBASE_AUTH_PORT,
);
firebaseFirestore.useFirestoreEmulator(
EMULATOR_HOST,
FIREBASE_PORT,
sslEnabled: false,
);
colUserRef = firebaseFirestore.collection('users');
user = BaseUser(
uid: '1234567890',
email: 'email#email.com',
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
);
});
group('Firestore Security Rules Tests -', () {
group('/users/* collection Tests-', () {
group('Unauthenticated Tests -', () {
/*
* Test if Unauthenticated User can list all users collection from firestore
* Should give permission-denied
* Rule LIST
*/
testWidgets('U: list users/*', (WidgetTester tester) async {
late String eCode;
try {
await colUserRef.get();
eCode = 'allowed';
} on FirebaseException catch (e) {
eCode = e.code;
}
expect(eCode, 'permission-denied');
});
});
});
});
}
Flutter Drive command that I use:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/sec_rules_test.dart \
--device-id web-server \
--dart-define=PROJECT_ID=someProjectId
Also by using IntegrationTestWidgetsFlutterBinding.ensureInitialized() I make sure:
I get my tests status logs
I get the fakeApp() to be close automatically when the tests finish.
If I don't use IntegrationTestWidgetsFlutterBinding, and use with web-server I get no logs.
If I don't use IntegrationTestWidgetsFlutterBinding, and use with chrome I get logs but the tests are executed twice.
If I don't use flutter drive for my tests, and use flutter test, I get Web devices are not supported for integration tests yet.
So basically I use flutter web integration tests for this to work.
What do you think about this approach? Should I go back and use a more mature security rules unit test with node? Do I have other possibilities to unit test my security rules?
I was thinking also to test these rules with restAPI calls from here and here, I think these are not within admin sdk context so they behave the same.
Do you see, maybe, something that I don't see within the possibility of these unit tests?
Anyhow, hope this helps other in my situation.
Related
I am trying to connect my AWS amplify data model to my flutter app and have followed all the configuration steps (as per this video: https://www.youtube.com/watch?v=9UjP_TJOY5E&t=642s), however, when I go to run the app in Chrome, I get the following error. error message.
I have imported all the relevant amplify packages:
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
And this is my configuration function:
void _configureAmplify() {
final provider = ModelProvider();
final dataStorePlugin = AmplifyDataStore(modelProvider: provider);
try {
Amplify.addPlugin(dataStorePlugin);
Amplify.configure(amplifyconfig);
debugPrint('Amplify Configured');
} catch (e) {
debugPrint('$e');
}
}
I have also tried running the app on an android emulator, but this does not work either.
Please let me know how I should approach fixing this problem. Thanks!
UPDATE: I also made this video to help you out --> https://www.youtube.com/watch?v=ooCc5wdVyMo
Addition to our discussions on the comment section. I recommend calling the _configureAmplify function from the main function. You can call it like the following:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await _configureAmplify();
runApp(const AmplifyGroceryListApp());
}
Future<void> _configureAmplify() async {
// Add the following lines to your app initialization to add the DataStore plugin
final datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance);
await Amplify.addPlugin(datastorePlugin);
try {
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
safePrint(
'Tried to reconfigure Amplify; this can occur when your app restarts on Android.');
}
}
I'm currently having an issue when I try to do unit test on a method that use Firestore.
This is the method that I want to mock
Future<MediasState> loadMedias(AbstractEvent event) async {
late AbstractBlocState streamState;
try {
DataHelper _dataHelperMediasEvent = DataHelperFactory.instance
.createInstanceFromAnotherDataHelperAndEntityInstance(
_dataHelperEvents, event, 'medias');
List<AbstractMedia> medias =
(await _dataHelperMediasEvent.getAll()).cast<AbstractMedia>();
for (AbstractMedia media in medias) {
media.user = await (_dataHelperUsers.getEntity(media.user.get()));
}
medias = sortEntitiesByCreatedDateDesc(medias) as List<AbstractMedia>;
streamState = MediasShowed(medias);
} catch (error) {
streamState = MediasShowedError();
} finally {
return streamState as MediasState;
}
}
And this the test function that I wrote
test('Test load medias', () async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
AbstractEvent event =
EntityFactory.createInstance('Event') as AbstractEvent;
AbstractMedia media1 =
EntityFactory.createInstance('Media') as AbstractMedia;
AbstractMedia media2 =
EntityFactory.createInstance('Media') as AbstractMedia;
AbstractMedia media3 =
EntityFactory.createInstance('Media') as AbstractMedia;
List<AbstractMedia> listOfMedias = <AbstractMedia>[];
listOfMedias.add(media1);
listOfMedias.add(media2);
listOfMedias.add(media3);
when(mockMediasEvent.loadMedias(event))
.thenAnswer((_) async => MediasShowed(listOfMedias));
blocMedias.add(LoadMediaOfOneEvent(event));
await expectLater(blocMedias.state, isA<MediasShowedError>());
});
Unfortunately I got this error when I run this test : "PlatformException(channel-error, Unable to establish connection on channel., null, null)
package:firebase_core_platform_interface/src/pigeon/messages.pigeon.dart 199:7 FirebaseCoreHostApi.initializeCore"
The DataHelperFactory create an instance of DataHelper which need an firestore instance.
class DataHelper {
DataHelper.initialize(String collectionPath) {
this.setCollection(collectionPath);
}
late String collectionPath;
late String className;
late CollectionReference<Map<String, dynamic>> collection;
final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
I already tried to upgrade my firebase dependencies but the problem is still there. I also tried on Windows and Mac device and on a friend device and I was able to reproduce the issue.
Any idea of how to fix my test will be welcome.
Thanks.
The documentation for flutter firebase testing states that mock firebase libraries need to be used. They are much easier to work with.
I’m initializing the app like
void main() async {
await Initializer.init();
runApp(MyApp());
}
Initializer.init() initializes all dependencies like Firebase Analytics and register things like Hive TypeAdapters.
My sample tests look like:
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'Sample',
(WidgetTester tester) async {
await Initializer.init();
await tester.pumpWidget(MyApp());
await tester.tap(find.text('Something'));
expect(find.text('180 Results'), findsOneWidget);
},
);
testWidgets(
'Sample 2',
(WidgetTester tester) async {
await Initializer.init();
await tester.pumpWidget(MyApp());
await tester.tap(find.text('Something 2'));
expect(find.text('180 Results'), findsOneWidget);
},
);
}
My problem is that after executing the first test, things that were initialized by Initializer.init() aren’t disposed correctly.
Is there a way to tell integration_test to reset the whole environment every time test is reloaded?
Shall I initialize things differently than static Initializer.init();
Shall I explicitly dispose/unregister all dependencies (sth like Initializer.dispose() at the end of every test)? That solution seems to be a nightmare since I'm using many 3rd parties that are not allowing explicit disposing.
I'm learning flutter on my own, and I'm using firebase on a small project. I'm in the stage of working with login and registrations, so I needed to use a plugin called firebase_auth: ^ 0.20.0 + 1.
However, I have reached a point in my code where one occurs, and I don't know what can cause it.
When I create the user, I indicate an email and a password in .createUserWithEmailAndPassword, if it works it should return a function to indicate success in the creation, there is no error log because I did not complete the code and performed this step, however the error is in the syntax that I'm using in this function, it doesn't seem to be in accordance with the syntax of firebase, I must be doing something wrong. He says these lines are wrong .then ((user) {firebaseUser = user;
import 'package:firebase_auth/firebase_auth.dart';
import 'package:scoped_model/scoped_model.dart';
import 'dart:async';
import 'package:flutter/material.dart';
class UserModel extends Model {
//usuario atual
FirebaseAuth _auth = FirebaseAuth.instance;
FirebaseUser firebaseUser;
Map<String, dynamic> userData = Map();
bool isLoading = false;
void signUp(Map<String, dynamic> userData, String pass, VoidCallback onSucess,
VoidCallback onFail) {
isLoading = true;
notifyListeners();
_auth
.createUserWithEmailAndPassword(
email: userData["email"], password: pass)
.then((user) async{
firebaseUser = user;
onSucess();
isLoading = false;
notifyListeners();
}).catchError((e) {
onFail();
isLoading = false;
notifyListeners();
});
}
void signIn() async {
isLoading = true;
notifyListeners();
await Future.delayed(Duration(seconds: 3));
isLoading = false;
notifyListeners();
}
void recoverPass() {}
//bool isLoggedIn() {}
}
class FirebaseUser {}
The call to createUserWithEmailAndPassword creates a user and returns a Future<UserCredential>.
In the then (which takes care of the Future part of this result), you assign the UserCredential to your firebaseUser variable, which is defined as FirebaseUser firebaseUser. And the error message tell you that FirebaseUser and UserCredential are not compatible types.
To get the FirebaseUser from the UserCredential, use:
.then((credentials) async{
firebaseUser = credentials.user;
Depending on the version of the firebase_auth plugin you use, you might need to declare firebaseUser as:
User firebaseUser
That is the correct type in the latest version of the FlutterFire libraries, while older builds had it as FirebaseUser.
I've linked the relevant reference documentation above, as I find that most helpful when troubleshooting these types of problems. I highly recommend keeping them open while you're learning about the API.
I have built an application and used firebase messaging package together with firebase cloud notifications. The notifications arrive and I am happy with that part. What I am also trying to do is to store those notifications to the database and provide a user with a handy section listing all those previous notifications directly inside of the app.
For the database, I am using the SQLite package which I was able to set up with no issues. I have made tests and it works (I have created store, update, delete procedures).
Now I am trying to connect those two together. So, upon iOS receiving the notification, I would like to run one of those procedures to store the notification inside of the database. However, it does not seem to be working and I am not sure what I am not grasping here. My code looks like this, just so you get the idea:
import 'package:flutter/material.dart';
import 'package:thw/utilities/constants.dart';
import 'package:thw/screens/main_screen.dart';
import 'package:thw/widgets/widgetNotifications.dart';
import 'package:thw/services/db.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
if (message.containsKey('data')) {
// Handle data message
final dynamic notification = message['data'];
print(notification);
await DbModel().dbOpen();
await DbModel().dbInsert(notification['title'], notification['body']);
}
if (message.containsKey('notification')) {
// Handle notification message
final dynamic notification = message['notification'];
print(notification);
await DbModel().dbOpen();
await DbModel().dbInsert(notification['title'], notification['body']);
}
}
class NotificationsScreen extends StatefulWidget {
#override
_NotificationsScreenState createState() => _NotificationsScreenState();
}
class _NotificationsScreenState extends State<NotificationsScreen> {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
List notifications;
#override
void initState() {
super.initState();
notifications = [];
getAllNotifications();
... more code
I have added this code on every single screen. I have loading, main screen and notifications screen. But this part does not seem to be working:
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
if (message.containsKey('data')) {
// Handle data message
final dynamic notification = message['data'];
print(notification);
await DbModel().dbOpen();
await DbModel().dbInsert(notification['title'], notification['body']);
}
if (message.containsKey('notification')) {
// Handle notification message
final dynamic notification = message['notification'];
print(notification);
await DbModel().dbOpen();
await DbModel().dbInsert(notification['title'], notification['body']);
}
}
I wonder what am I doing wrong? Am I having a bad grasp of how this works? The notifications arrive, but the part with the dbOpen and dbInsert never seems to run.