Coder Social home page Coder Social logo

fake_cloud_firestore's Introduction

Fake Cloud Firestore

pub package Unit Tests Buy Me A Coffee

Fakes to write unit tests for Cloud Firestore. Instantiate a FakeFirebaseFirestore, then pass it around your project as if it were a FirebaseFirestore. This Fake acts like Firestore except that will only keep the data in memory. To help debug, you can use FakeFirebaseFirestore.dump() to see what's in the fake database. This is useful to set up the state of your database, then check that your UI behaves the way you expect.

This project works well with firebase_auth_mocks. If you use them together, you can even test your app with security rules. See below.

Usage

Simple example

import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';

void main() {
  final instance = FakeFirebaseFirestore();
  await instance.collection('users').add({
    'username': 'Bob',
  });
  final snapshot = await instance.collection('users').get();
  print(snapshot.docs.length); // 1
  print(snapshot.docs.first.get('username')); // 'Bob'
  print(instance.dump());
}

// Prints out:
// {
//   "users": {
//     "z": {
//       "name": "Bob"
//     }
//   }
// }

See more examples at fake_cloud_firestore/test/fake_cloud_firestore_test.dart.

In a UI test

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:firestore_example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

const MessagesCollection = 'messages';

void main() {
  testWidgets('shows messages', (WidgetTester tester) async {
    // Populate the fake database.
    final firestore = FakeFirebaseFirestore();
    await firestore.collection(MessagesCollection).add({
      'message': 'Hello world!',
      'created_at': FieldValue.serverTimestamp(),
    });

    // Render the widget.
    await tester.pumpWidget(MaterialApp(
        title: 'Firestore Example', home: MyHomePage(firestore: firestore)));
    // Let the snapshots stream fire a snapshot.
    await tester.idle();
    // Re-render.
    await tester.pump();
    // // Verify the output.
    expect(find.text('Hello world!'), findsOneWidget);
    expect(find.text('Message 1 of 1'), findsOneWidget);
  });
}

See more examples at fake_cloud_firestore/example/test/widget_test.dart.

Exceptions

Based on the state

Most features automatically throw exceptions depending on the state, so you do not need to mock them. For example, calling DocumentSnapshot.get on a missing field will throw a StateError.

final firestore = FakeFirebaseFirestore();
final collection = firestore.collection('test');
final doc = collection.doc('test');
await doc.set({
  'nested': {'field': 3}
});

final snapshot = await doc.get();

expect(() => snapshot.get('foo'), throwsA(isA<StateError>()));
expect(() => snapshot.get('nested.foo'), throwsA(isA<StateError>()));

Mocking exceptions

Furthermore, some methods allow mocking exceptions manually, for example to simulate network errors. You can even set conditions on the parameters using the standard Dart matchers. Here are the methods which support mocking exceptions:

  • DocumentReference.get, set, update, delete.
  • Query.get.
final instance = FakeFirebaseFirestore();
final doc = instance.collection('users').doc(uid);
whenCalling(Invocation.method(#set, null))
    .on(doc)
    .thenThrow(FirebaseException(plugin: 'firestore'));
expect(() => doc.set({'name': 'Bob'}), throwsA(isA<FirebaseException>()));

For examples of how to set conditions on when to throw an exception, see firebase_auth_mocks#throwing-exceptions.

Security Rules

You can pass the security rules that you use in production in Firestore to [FakeFirebaseFirestore]. When operating on [DocumentReference] using get, set, update, or delete, [FakeFirebaseFirestore] will then check security rules and throw exceptions if access is restricted. In the example below, we restrict users/{userId} documents to their respective owners. Before they sign in, they cannot access any document inside the users collection. Once they sign in, they have access to only their own users/[uid] document.

import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:test/test.dart';

// https://firebase.google.com/docs/rules/rules-and-auth#leverage_user_information_in_rules
final authUidDescription = '''
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}''';

main() async {
  test('security rules' {
    final auth = MockFirebaseAuth();
    final firestore = FakeFirebaseFirestore(
        // Pass security rules to restrict `/users/{user}` documents.
        securityRules: authUidDescription,
        // Make MockFirebaseAuth inform FakeFirebaseFirestore of sign-in
        // changes.
        authObject: auth.authForFakeFirestore);
    // The user signs-in. FakeFirebaseFirestore knows about it thanks to
    // `authObject`.
    await auth.signInWithCustomToken('some token');
    final uid = auth.currentUser!.uid;
    // Now the user can access their user-specific document.
    expect(
        () => firestore.doc('users/$uid').set({'name': 'abc'}), returnsNormally);
    // But not anyone else's.
    expect(() => firestore.doc('users/abcdef').set({'name': 'abc'}),
        throwsException);
  });
}

See https://github.com/atn832/fake_cloud_firestore/blob/master/test/security_test.dart for more examples of security rules.

Right now we only support operations on DocumentReference. Later we will implement security checks for batch requests, collections and queries. Furthermore, we do not support timestamps and durations yet. See Fake Firebase Rules for an exhaustive list of what is and is not supported.

Features

  • Dump the state of the fake firebase with FakeFirebaseFirestore.dump().
  • Clear the fake firebase with FakeFirebaseFirestore.clearPersistence().
  • Create documents and collections.
  • Supports Converters.
  • Create documents with collection.add or document.set.
  • Batch writes and runTransaction.
  • Query documents with collection.snapshots or query.get.
  • Queries:
    • Filter results with query.where. The library supports equals, isGreaterThan, isGreaterThanOrEqualTo, isLessThan,isLessThanOrEqualTo, isNull, isNotEqualTo, arrayContains, arrayContainsAny, whereIn and whereNotIn.
    • Sort results with query.orderBy.
    • Limit results with query.limit, limitToLast, startAfterDocument, startAt, startAtDocument, endAt, endAtDocument, endBefore, endBeforeDocument. Note: startAnd and endAt work only on exact matches.
    • Aggregate with query.count, query.aggregate(sum(field1), average(field2)...).
  • ValueField:
    • set timestamps with FieldValue.serverTimestamp(). Optionally you can pass a Clock when instantiating FakeFirebaseFirestore to define what time gets set.
    • delete values with FieldValue.delete().
    • update numerical values with FieldValue.increment.
    • update arrays with FieldValue.arrayUnion and FieldValue.arrayRemove.
  • Mock exceptions for DocumentReference.set.
  • Security rules:
    • Initialize FakeFirebaseFirestore with custom security rules.
    • FakeFirebaseFirestore takes authentication state from firebase_auth_mocks into account.
    • DocumentReference.get, set, update and delete are protected.

Compatibility table

cloud_firestore fake_cloud_firestore
5.0.0 3.0.0
4.17.0 2.5.0
4.4.0 2.4.0
4.0.0 2.0.0
3.4.0 1.3.0
3.0.0 1.2.1
2.2.0 1.1.0
2.1.0 1.0.2
1.0.0 0.8.4
0.16.0 0.6.0
0.14.0 0.5.0
0.13.1+1 0.4.1
0.13.0+1 0.2.5
^0.12.9+6 0.2.0

Features and bugs

Please file feature requests and bugs at the issue tracker.

fake_cloud_firestore's People

Contributors

agent3bood avatar alexhartford avatar amatveiakin avatar andrew-bekhiet avatar anu-rock avatar atn832 avatar cedvdb avatar danreynolds avatar dfdgsdfg avatar dumbbell avatar feroult avatar gaburielcasado avatar gki avatar hadii1 avatar jouby avatar kholmatovs avatar lyledean1 avatar maxluchterhand1 avatar ming-chu avatar mrunix00 avatar nukotsuka avatar qwales1 avatar sapphirewhale avatar sensuikan1973 avatar suztomo avatar sydneyagcaoili avatar yukio-output avatar zambrella avatar zariweyo avatar zohenn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fake_cloud_firestore's Issues

documentSnapshot.data should be null before the document is saved

Test case:

    final snapshot1 =
        await firestore.collection('users').document(nonExistentId).get();
    expect(snapshot1, isNotNull);
    expect(snapshot1.documentID, nonExistentId);
    // data field should be null before the document is saved
    expect(snapshot1.data, isNull);

As of now, this test fails because snapshot1.data is an empty Map. Real Firestore has null there.

Screen Shot 2020-02-25 at 7 41 05 PM

Integration tests in the example project fail to compile on Android

The fix is to add

android.useAndroidX=true
android.enableJetifier=true

to gradle.properties, as explained in https://flutter.dev/docs/development/androidx-migration#how-do-i-know-if-my-project-is-using-androidx.

Error log:

example % flutter drive --target=test_driver/cloud_firestore.dart
Using device Android SDK built for x86.
Starting application: test_driver/cloud_firestore.dart
Installing build/app/outputs/apk/app.apk...                         2.8s
[!] Your app isn't using AndroidX.
    To avoid potential build failures, you can quickly migrate your app by following the steps on https://goo.gl/CP92wY.
Note: ~/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.13.1+1/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.                      
/[somepath]/cloud_firestore_mocks/example/android/app/src/main/AndroidManifest.xml:22:18-91 Error:
        Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
        is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
        Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:6:5-18:19 to override.
                                                                        
FAILURE: Build failed with an exception.

Missing support for query.arrayContainsAny

MockQuery presently supports an impressive list of actual Firestore queries. The one that is sorely missing is arrayContainsAny, which is like doing multiple arrayContains queries simultaneously.

I believe providing an implementation for this will not be too complicated or time consuming. Please consider adding support for it.

cloud_firestore_behaviors fail when casting to Map<String, dynamic>

Looks like the actual Firebase implementation doesn't return a Map<String, dynamic>, so the tests crash when casting to Map<string, dynamic>.

https://gist.github.com/atn832/5dcae11307d4c59de143a3259dbb87cf

flutter: 00:02 +9: Firestore behavior comparison: Document creation by setData (Cloud Firestore) [E]
flutter: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>' in type cast
flutter: Users/anhtuan/git/cloud_firestore_mocks/test_driver/cloud_firestore_behaviors.dart 138:43 main..

runTransaction: check the return value of the map

Returning a document reference in transaction handler's return value causes PlatformException(error, Invalid argument: Instance of 'DocumentReference', null):

      final result = await _firestore.runTransaction((Transaction tx) async {
...
        // In cloud_firestore, the transaction handler should return void
        // or a map somehow. The value of the Map cannot be Person.
        // https://github.com/FirebaseExtended/flutterfire/issues/1642
        return <String, dynamic>{
          'personId': personId,
          'noteDocumentReference': noteDocumentReference,
        };
      });

Support StartAfter(List<dynamic> values)

Seems not support startAfter(List values).

int d1 = 1;
int d2 = 2;
int d3 = 3;

var collectionRef =
    Firestore.instance.collection("users/");

await collectionRef.add({'created': d1});
await collectionRef.add({'created': d2});
await collectionRef.add({'created': d3});

var results =
    await collectionRef.orderBy('created', descending: true).startAfter([d2]).getDocuments();

results.documents.forEach((d) => print(d.data['created']));

Migrate android/ to AndroidX

Otherwise I get the following error when running integration tests in Android:

% flutter drive --target=test_driver/cloud_firestore_behaviors.dart
Using device Android SDK built for x86.
Starting application: test_driver/cloud_firestore_behaviors.dart
Installing build/app/outputs/apk/app.apk...                         2.9s
[!] Your app isn't using AndroidX.
    To avoid potential build failures, you can quickly migrate your app by following the steps on https://goo.gl/CP92wY.
Note: /Users/anhtuan/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.13.5/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.                      
/Users/anhtuan/git/cloud_firestore_mocks/android/app/src/main/AndroidManifest.xml:22:18-91 Error:
        Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
        is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
        Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:6:5-18:19 to override.
                                                                        
FAILURE: Build failed with an exception.                                
                                                                        
* What went wrong:                                                      
Execution failed for task ':app:processDebugManifest'.                  
> Manifest merger failed : Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
        is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
        Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:6:5-18:19 to override.
                                                                        
* Try:                                                                  
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
                                                                        
* Get more help at https://help.gradle.org                              
                                                                        
BUILD FAILED in 10s                                                     
Running Gradle task 'assembleDebug'...                                  
Running Gradle task 'assembleDebug'... Done                        10.9s
AndroidX incompatibilities may have caused this build to fail. Please migrate your app to AndroidX. See https://goo.gl/CP92wY.
Gradle task assembleDebug failed with exit code 1

The fix seems to be to add:

android.enableJetifier=true
android.useAndroidX=true

to https://github.com/atn832/cloud_firestore_mocks/blob/master/android/gradle.properties.

Map returned from `MockDocumentSnapshot.data` should be immutable

The object returned from the actual Firestore implementation of DocumentSnapshot.data getter is a Map<String, dynamic> that apparently does not respond to value changes.

For example, something like this presently works with cloud_firestore_mocks but not with the real cloud_firestore:

final snapshot = await store.collection('products').document(productId).get();
// snapshot.data is a Map like { 'id': null, 'name': 'iPhone SE', 'price': 399.0 }
snapshot.data['id'] = snapshot.documentID;
return snapshot.data; // 'id' in returned Map is still null (with real Firestore)

A way around with real Firestore is:

final snapshot = await store.collection('products').document(productId).get();
final data = snapshot.data;
data['id'] = snapshot.documentID;
return data; // 'id' in returned Map is now correctly set

This is weird as snapshot.data and the new data variables both essentially point to the same Map. But it works for some reason.

This is a small quirk that MockDocumentSnapshot.data should also handle. Because of the current implementation, my unit tests that expect "id" to be set correctly passed with cloud_firestore_mocks but my logic with cloud_firestore failed.

Updating a document should not affect previous snapshots

Test case:

  test('Snapshot should remain after updating data', () async {
    final firestore = MockFirestoreInstance();
    // These documents are not saved
    final reference = firestore.collection('users').document('foo');
    await reference.setData(
      <String, dynamic>{'name': 'old'}
    );
    final snapshot1 = await reference.get();

    await reference.setData(
        <String, dynamic>{'name': 'new'}
    );

    expect(snapshot1.data['name'], 'old');
  });

I guess I'll need to implement deep copy of Firestore documents.

Stop extending Mock

Extending Mock makes it possible to call Mockito methods like when even though that's not how the library should be used. See #26.

collection('...').document() should not save a document

With current implementation, the following test code fails:

    final reference1 = firestore.collection('users').document();
    final reference2 = firestore.collection('users').document();

    await reference1.setData({
      'someField': 'someValue',
    });

    QuerySnapshot querySnapshot = await firestore.collection('users').getDocuments();
    // Only reference1 is saved
    expect(querySnapshot.documents, hasLength(1));
    expect(querySnapshot.documents.first['someField'], 'someValue');

It fails because as of now cloud_firestore_mocks does not distinguish saved and unsaved documents.

I suspect code below should be updated to accommodate this.

  @override
  DocumentReference document([String path]) {
    if (path == null) {
      path = _generateAutoId();
    }

    return MockDocumentReference(path, getSubpath(root, path), root,
        getSubpath(snapshotStreamControllerRoot, path));
  }

getSubpath(root, path) creates a document to the root.

isEqualTo doesn't work when comparing (Mock)DocumentReferences

The following test fails:

test('isEqualTo should work with DocumentReferences', () async {
     final instance = MockFirestoreInstance();
     await instance.collection('users').document('123').setData({
       'name': 'Alex'
     });
     await instance.collection('courses').document('123').setData({
       'title': 'Learn flutter & Dart the easy way'
     });
     await instance.collection('usercourses').add({
       'user': instance.document('users/123'),
       'course': instance.document('courses/123'),
       'started_at': DateTime.now()
     });

     var query = instance.collection('usercourses')
      .where('user', isEqualTo: instance.document('users/123'));

    query.snapshots().listen(expectAsync1(
        (event) {
          expect(event.documents.length, greaterThan(0));
        },
      ));
  });

Add Equality on MockDocumentReference

    test('FieldValue.arrayUnion() adds unique items', () async {
      final firestore = MockFirestoreInstance();
      // Empty document before updateData
      await firestore.collection('messages').document(uid).setData({
        'reference array': [
          firestore.collection('messages').document('document1')
        ]
      });

      await firestore.collection('messages').document(uid).updateData({
        'reference array': FieldValue.arrayUnion(
            [firestore.collection('messages').document('document1')]),
      });

      final users = await firestore.collection('messages').getDocuments();
      final snapshot = users.documents.first;

      expect(snapshot['reference array'], [
        firestore.collection('messages').document('document1')
      ]);
    });

This failed:

Expected: [MockDocumentReference:MockDocumentReference]
  Actual: [MockDocumentReference:MockDocumentReference, null]
   Which: was MockDocumentReference:<MockDocumentReference> instead of MockDocumentReference:<MockDocumentReference> at location [0]

MockFirestoreInstance.dump() crashes on GeoPoint

This test fails:

await firebase.collection('places').add({
  location: GeoPoint(10, 100)
});
final snapshot = await firebase.collection('places').getDocuments();
final document = snapshot.documents.first;
expect(document['location'], equals(GeoPoint(10, 100)); // this works
expect(firebase.dump(), contains('location: ...')); // this does not.

Stubbing deep chaining calls

Is there any workaround for chaining calls?

main() {
  group('A', () {
    test('return right item', () async {
      final firestore = MockFirestoreInstance();

      await firestore.collection('A').document('1').setData(preparedData1);
      await firestore.collection('A').document('2').setData(preparedData2);

      // Fail. Mockito does not support deep stubbing
      var res1 = await firestore.collection('A').document('1').get();
      when(firestore.collection('rooms').document('1').snapshots())
          .thenAnswer((_) => Stream.value(res1));
      
      // Mokcito suggest way which is below. Also fail.
      var docSnap = MockDocumentSnapshot('1', res1.data);
      when(docSnap).thenAnswer((_) => Stream.value(res1));

      final api = SomeApi(firestore);
      api.getStreamA("1").listen(expectAsync1((res) {
        expect(res.documentID, equals("1"));
      }));
    });
  });
}
  1. Deep Stubbing
    dart-lang/mockito#21

  2. Mockito Suggest
    https://github.com/dart-lang/mockito#how-it-works

var mockUtils = MockUtils();
var mockStringUtils = MockStringUtils();

// Setting up mockUtils.stringUtils to return a mock StringUtils implementation
when(mockUtils.stringUtils).thenReturn(mockStringUtils);

// Some tests

// FAILS!
verify(mockUtils.stringUtils.uppercase()).called(1);
// Instead use this:
verify(mockStringUtils.uppercase()).called(1);

DocumentReference through FirestoreInstance.document() does not save document

This test fails at 0.4.2:

  test('Saving documents through FirestoreInstance.document()',
      () async {
    final instance = MockFirestoreInstance();
    // Creates 1st document in "users/abc/friends/<documentId>"
    await instance.document('users/$uid/friends/xyz').setData({
      'name': 'Foo',
      'nested': {
        'k1': 'v1',
      }
    });

    final documentReference = instance
        .collection('users')
        .document(uid)
        .collection('friends')
        .document('xyz');

    final snapshot = await documentReference.get();
    expect(snapshot.data['name'], 'Foo');
    final nested = snapshot.data['nested'] as Map<String, dynamic>;
    expect(nested['k1'], 'v1');
  });

setData with keys containing dots should create nested documents

documentReference.setData with keys containing dots should create nested documents. Test case to confirm the expected behavior:

import 'package:cloud_firestore_mocks/cloud_firestore_mocks.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
    testWidgets('Nested documents created by setData with keys containing dots',
      (WidgetTester tester) async {
    final fakeFirestore = MockFirestoreInstance()..setupFieldValueFactory();

    final documentRef1 = fakeFirestore
        .collection('users')
        .document();
    final createdTime =  DateTime.now();
    await documentRef1.setData(<String, dynamic>{
      'favorites.keyA.name': 'Banana',
      'favorites.keyA.created': createdTime,
    });
    await documentRef1.setData(<String, dynamic>{
      'favorites.keyB.name': 'Apple',
      'favorites.keyB.created': createdTime,
    });
    
    final snapshot = await documentRef1.get();
    expect(snapshot['favorites'], isNotNull); // This fails.
    final favoritesMap = snapshot['favorites'] as Map<dynamic, dynamic>;
    expect(favoritesMap.keys as Iterable<String>, hasLength(2));

    final bananaMap = favoritesMap['keyA'] as Map<dynamic, dynamic>;
    expect(bananaMap['name'] as String, 'Banana');
    expect(bananaMap['created'] as DateTime, createdTime);
  });
}

Screenshot from real Firebase (from a different example):

Screen Shot 2020-02-23 at 11 48 49 PM

I might contribute a PR to this issue.

MockQuery should defer query execution

MockQuery should defer query execution. In this example code, subcollection is updated twice. In real Firebase, querying the same subcollection should work.

  test('Saving documents in subcollection', () async {
    final instance = MockFirestoreInstance();
    await instance
        .collection('users')
        .document(uid)
        .collection('friends')
        .add(<String, dynamic>{'name': 'Foo'});

    final documentReference = instance
        .collection('users')
        .document(uid)
        .collection('friends')
        .document();

    var subcollection =
        instance.collection('users').document(uid).collection('friends');
    var querySnapshot = await subcollection.getDocuments();
    expect(querySnapshot.documents, hasLength(1));

    // Only after setData, the document is available for getDocuments
    await documentReference.setData({'name': 'Bar'});

    // This line below should be unnecessary.
    subcollection =
        instance.collection('users').document(uid).collection('friends');
    querySnapshot = await subcollection.getDocuments();
    expect(querySnapshot.documents, hasLength(2));

Support sub collection

Seems not support nested sub collection.

firestore = MockFirestoreInstance();
      await firestore
          .collection('userProfiles')
          .document('a')
          .collection('relationship')
          .document('1')
          .setData(relationship1);
      await firestore
          .collection('userProfiles')
          .document('a')
          .collection('relationship')
          .document('2')
          .setData(relationship2);
      await firestore
          .collection('userProfiles')
          .document('a')
          .collection('relationship')
          .document('3')
          .setData(relationship3);
      await firestore
          .collection('userProfiles')
          .document('a')
          .collection('relationship')
          .document('4')
          .setData(relationship4);

      relsApi.allRelationships('a').listen(expectAsync1((rs) {
        print(rs.map((r) => r.relId));  // -> (1, 1, 1, 1) should be(1,2,3,4)
        expect(rs.length, equals(4)); 
      }));

Restore support for `FieldValue.delete()`

In #9, I removed support for FieldValue.delete() because of a change in cloud_firestore's API.

To restore the feature, I have to figure out how to decode a FieldValue.

cloud_firestore_platform_interface seems to have done it in its tests:

But maybe they're running tests on a different layer, so I can't do it the same way.

When the fix is properly implemented, this test should pass:

    test('FieldValue.delete() deletes key values', () async {
      final firestore = MockFirestoreInstance();
      firestore.setup();
      await firestore.document('root').setData({
        'flower': 'rose'
      });
      await firestore.document('root').setData({
        'flower': FieldValue.delete()
      });
      // expect 'flower' not to be present
    });

[ERROR: Bad state] Mocking response with when()

Trying to mock when().then() but I get an error:
ERROR: Bad state: Mock method was not called within when(). Was a real method called?

test(
      'should return user document',
      () async {
        when(mockService.collection(any).document(any)).thenAnswer(
          (_) => tUserDoc,
        );
        // expect
        // verify
      },

Firestore.document(...) should fail if the number of segments is not even

https://firebase.google.com/docs/firestore/data-model

For convenience, you can also create references by specifying the path to a document or collection as a string, with path components separated by a forward slash (/)

    final firestore = Firestore.instance;
   // 2020-03-01 19:54:20.127922-0500 Runner[59725:2435047] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'FIRESTORE INTERNAL ASSERTION FAILED: Invalid document reference. Document references must have an even number of segments, but foo has 1'
    final abc = firestore.document('abc');

Interestingly, this is not a Dart exception. Due to the NSInternalInconsistencyException, the app fails.

https://stackoverflow.com/questions/53918354/inconsistent-flutter-behavior-with-native-ios-exceptions

Maybe assert would suffice.

Nested object support in MockQuery

  await fs.collection("tests").add({"testSubObject": {"key": "abc"}});

var result = await fs.collection("tests").where("testSubObject.key", isEqualTo: "abc").getDocuments();

    print(result.documents.length);

Right now this returns 0 documents, whilst in FireStore it returns 1 document.

This has lead to some of our code becoming impossible to test with cloud_firestore_mocks, though I am not sure how many people actually use this feature...

UpdateData should throw PlatformException if no existing document

As in the #39 's test case, updateData should throw PlatformException if there's no document.

https://github.com/atn832/cloud_firestore_mocks/pull/39/files#diff-bc4857fb99d367ed497ed395a5dfa274R98

flutter: 00:01 +7 -2: Firestore behavior comparison: Nested objects update (Cloud Firestore) [E]
flutter:   PlatformException(Error 5, FIRFirestoreErrorDomain, No document to update:
projects/flutter-firestore/databases/(default)/documents/messages/UYK7FkXENdf7mshYHrjR)

collection.document() to assign a random ID to the document reference

It would be great upon documentRefeference.setData, the document reference has a random String ID within the collection.

My expectation in the following test:

import 'package:cloud_firestore_mocks/cloud_firestore_mocks.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('documentRefeference.setData should set documentId',
      (WidgetTester tester) async {
    final fakeFirestore = MockFirestoreInstance()..setupFieldValueFactory();

    final documentRef1 = fakeFirestore
        .collection('users')
        .document();
    await documentRef1.setData(<String, dynamic>{
      'content': 'MockFirestoreInstance is great',
      'createdBy': 'Tomo',
    });
    
    expect(documentRef1.documentID, isNotNull); // This fails.

    final documentRef2 = fakeFirestore
        .collection('users')
        .document();
    await documentRef2.setData(<String, dynamic>{
      'content': 'Mock tests are good',
      'createdBy': 'Joichiro',
    });
    expect(documentRef2.documentID, isNotNull);

    expect(documentRef2.documentID, isNot(documentRef1.documentID));
  });
}

orderBy throws when field is null

Not sure if this is expected behavior, but this test fails:

 test('my tests', () async {
     final instance = MockFirestoreInstance();
     await instance.collection('usercourses').add({
       'started_at': Timestamp.fromDate(DateTime.now().subtract(Duration(days: 4))),
       'completed_at': Timestamp.fromDate(DateTime.now())
     });
     await instance.collection('usercourses').add({
       'started_at': Timestamp.fromDate(DateTime.now().subtract(Duration(days: 5))),
       'completed_at': null
     });

    var query = instance.collection('usercourses')
      .orderBy('completed_at');

    query.snapshots().listen(expectAsync1(
        (event) {
          expect(event.documents.length, greaterThan(0));
        },
      ));
  });
ERROR: NoSuchMethodError: The getter 'seconds' was called on null.
Receiver: null
Tried calling: seconds
dart:core                                                            Object.noSuchMethod
package:cloud_firestore_platform_interface/src/timestamp.dart 89:26  Timestamp.compareTo
package:cloud_firestore_mocks/src/mock_query.dart 51:32              MockQuery.orderBy.<fn>.<fn>
dart:collection                                                      ListMixin.sort
package:cloud_firestore_mocks/src/mock_query.dart 48:18              MockQuery.orderBy.<fn>
package:cloud_firestore_mocks/src/mock_query.dart 35:23              MockQuery.getDocuments

I would expect that there would still be results sorted from fields with values to fields that are null. I think it works like that with regular firebase queries as well.

DocumentReference.parent() unexpectedly returning null

This test fails at 0.4.2:

  test('Document reference path', () async {
    final instance = MockFirestoreInstance();
    final documentReference = instance
        .collection('users')
        .document('aaa')
        .collection('friends')
        .document('bbb')
        .collection('friends-friends')
        .document('ccc');

    final friendsFriends = documentReference.parent(); // null
    final bbb = friendsFriends.parent();
    final friends = bbb.parent();
    final bbbSibling = friends.document('bbb-sibling');
    expect(bbbSibling.path, 'users/aaa/friends/bbb-sibling');
  });

Real Firebase project prevents .where(arrayContains:) requests

I/flutter (19814): 00:01 +0: Where: array-contains (Cloud Firestore) [E]
I/flutter (19814):   PlatformException(Error performing setData, PERMISSION_DENIED: Missing or insufficient permissions., null)
I/flutter (19814):   package:flutter/src/services/message_codecs.dart 569:7        StandardMethodCodec.decodeEnvelope
I/flutter (19814):   package:flutter/src/services/platform_channel.dart 321:33     MethodChannel.invokeMethod
I/flutter (19814):   ===== asynchronous gap ===========================
I/flutter (19814):   dart:async/zone.dart 1064:19                                  _CustomZone.registerBinaryCallback
I/flutter (19814):   dart:async-patch/async_patch.dart 82:23                       _asyncErrorWrapperHelper
I/flutter (19814):   package:test_api/src/backend/invoker.dart                     Invoker.waitForOutstandingCallbacks.<fn>
I/flutter (19814):   dart:async/zone.dart 1126:13                                  _rootRun
I/flutter (19814):   dart:async/zone.dart 1023:19                                  _CustomZone.run
I/flutter (19814):   dart:async/zone.dart 1518:10                                  _runZoned
I/flutter (19814):   dart:async/zone.dart 1465:12                                  runZoned
I/flutter (19814):   package:test_api/src/backend/invoker.dart 239:5               Invoker.waitForOutstandingCallbacks
I/flutter (19814):   package:test_api/src/backend/declarer.dart 169:33             Declarer.test.<fn>.<fn>

Perhaps we should create a Firebase project of our own and set up the right permissions.

Missing support for query.startAt and query.endAt

Firestore does not natively support SQL's LIKE-style queries. That means, we cannot do something like this:

var docs = await db.collection('products').where('name', like: keyword).getDocuments();

As per official Firebase docs, the solution is to add full-text search through a third-party service.

This is an overkill for simple use cases. Another solution floating around on stackoverflow is to leverage startAt and endAt query methods, something like this:

var docs = await db.collection("users").startAt(searchText).endAt(searchText+ "\uf8ff").getDocuments();

cloud_firestore_mocks's MockQuery class does not support these methods at the moment. It'd be cool to have them to be able to test such use cases. Please consider adding them.

Error: Class 'FieldValue' has no instance getter '_delegate' with cloud_firestore: 0.13.4

When I upgraded cloud_firestore: 0.13.4 in my project, one of the existing tests throws the following error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following NoSuchMethodError was thrown running a test:
Class 'FieldValue' has no instance getter '_delegate'.
Receiver: Instance of 'FieldValue'
Tried calling: _delegate

When the exception was thrown, this was the stack:
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1      FieldValuePlatform.getDelegate (package:cloud_firestore_platform_interface/src/platform_interface/field_value.dart:19:18)
#2      MockDocumentReference.updateData.<anonymous closure> (package:cloud_firestore_mocks/src/mock_document_reference.dart:57:50)
#3      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
#4      MockDocumentReference.updateData (package:cloud_firestore_mocks/src/mock_document_reference.dart:49:10)
#5      _DummyTransaction.update (package:cloud_firestore_mocks/src/cloud_firestore_mocks_base.dart:158:30)
#6      TagsModel.save.<anonymous closure> (package:hitomemo/person_tags_model.dart:185:18)
<asynchronous suspension>
#7      MockFirestoreInstance.runTransaction (package:cloud_firestore_mocks/src/cloud_firestore_mocks_base.dart:63:51)
#8      TagsModel.save (package:hitomemo/person_tags_model.dart:173:39)
#9      main.<anonymous closure> (file:///Users/suztomo/Documents/hitomemo/test/tags_model_test.dart:56:30)
<asynchronous suspension>
...

Support Firestore.runTransaction

(I haven’t tested but I assume the function is not implemented yet. So far I don’t use transactions in my Flutter app)

https://firebase.google.com/docs/firestore/manage-data/transactions#transactions

final DocumentReference postRef = Firestore.instance.document('posts/123');
Firestore.instance.runTransaction((Transaction tx) async {
  DocumentSnapshot postSnapshot = await tx.get(postRef);
  if (postSnapshot.exists) {
    await tx.update(postRef, <String, dynamic>{'likesCount': postSnapshot.data['likesCount'] + 1});
  }
});

Excerpt from https://pub.dev/packages/cloud_firestore

conflict with cloud_firestore plugin

there's a version issue between cloud_firestore and cloud_firestore_mocks.
want a new version of cloud_firestore_mocks

Because every version of cloud_firestore_mocks depends on cloud_firestore ^0.12.9+6 and <project name> depends on cloud_firestore ^0.13.0, cloud_firestore_mocks is forbidden.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.