Skip to content

Commit 93da19a

Browse files
authored
Add test helpers to run tests against Firestore emulator locally (#755)
1 parent bec44c6 commit 93da19a

File tree

2 files changed

+114
-47
lines changed

2 files changed

+114
-47
lines changed

firestore/testapp/Assets/Firebase/Sample/Firestore/UIHandler.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,59 @@ public override bool keepWaiting {
201201

202202
protected internal FirebaseFirestore db {
203203
get {
204-
return FirebaseFirestore.DefaultInstance;
204+
FirebaseFirestore firestore = FirebaseFirestore.DefaultInstance;
205+
SetTargetBackend(firestore);
206+
return firestore;
205207
}
206208
}
207209

210+
protected internal FirebaseFirestore TestFirestore(FirebaseApp app) {
211+
FirebaseFirestore firestore = FirebaseFirestore.GetInstance(app);
212+
SetTargetBackend(firestore);
213+
return firestore;
214+
}
215+
216+
// Update the `Settings` of a Firestore instance to run tests against the production or
217+
// Firestore emulator backend.
218+
protected internal void SetTargetBackend(FirebaseFirestore db) {
219+
string targetHost = GetTargetHost();
220+
221+
// Avoid updating `Settings` if not required. No changes are allowed to be made to the
222+
// settings of a <c>FirebaseFirestore</c> instance if it has invoked any non-static method.
223+
/// Attempting to do so will result in an exception.
224+
if (db.Settings.Host == targetHost) {
225+
return;
226+
}
227+
228+
db.Settings.Host = targetHost;
229+
// Emulator does not support ssl.
230+
db.Settings.SslEnabled = IsUsingFirestoreEmulator() ? false : true;
231+
}
232+
233+
private string GetTargetHost() {
234+
if (IsUsingFirestoreEmulator()) {
235+
#if UNITY_ANDROID
236+
// Special IP to access the hosting OS from Android Emulator.
237+
string localHost = "10.0.2.2";
238+
#else
239+
string localHost = "localhost";
240+
#endif // UNITY_ANDROID
241+
242+
// Use FIRESTORE_EMULATOR_PORT if it is set to non empty string, otherwise use the
243+
// default port.
244+
string port = Environment.GetEnvironmentVariable("FIRESTORE_EMULATOR_PORT") ?? "8080";
245+
return localHost + ":" + port;
246+
}
247+
248+
return FirebaseFirestore.DefaultInstance.Settings.Host;
249+
}
250+
251+
// Check if the `USE_FIRESTORE_EMULATOR` environment variable is set regardsless
252+
// of its value.
253+
protected internal bool IsUsingFirestoreEmulator() {
254+
return (Environment.GetEnvironmentVariable("USE_FIRESTORE_EMULATOR") != null);
255+
}
256+
208257
// Cancel the currently running operation.
209258
protected void CancelOperation() {
210259
if (operationInProgress && cancellationTokenSource != null) {

firestore/testapp/Assets/Firebase/Sample/Firestore/UIHandlerAutomated.cs

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ protected override void Start() {
164164
Func<Task>[] testFilter = {
165165
// THIS LIST MUST BE EMPTY WHEN CHECKED INTO SOURCE CONTROL!
166166
};
167-
167+
168168
// Unity "helpfully" adds stack traces whenever you call Debug.Log. Unfortunately, these stack
169169
// traces are basically useless, since the good parts are always truncated. (See comments on
170170
// LogInBatches.) So just disable them.
@@ -191,6 +191,14 @@ protected override void Start() {
191191

192192
UIEnabled = false;
193193
base.Start();
194+
195+
/*
196+
* THIS MUST BE COMMENTED OUT WHEN CHECKED INTO SOURCE CONTROL!
197+
*
198+
* To run tests against Firestore emulator locally, set `USE_FIRESTORE_EMULATOR` to "true".
199+
* To switch back to run against prod, set it back to null.
200+
*/
201+
// Environment.SetEnvironmentVariable("USE_FIRESTORE_EMULATOR", "true");
194202
}
195203

196204
// Passes along the update call to automated test runner.
@@ -747,7 +755,7 @@ Task TestMultiInstanceSnapshotsInSyncListeners() {
747755
var app1 = db1.App;
748756

749757
var app2 = FirebaseApp.Create(app1.Options, "MultiInstanceSnapshotsInSyncTest");
750-
var db2 = FirebaseFirestore.GetInstance(app2);
758+
var db2 = TestFirestore(app2);
751759
var db2Doc = db2.Collection(db1Doc.Parent.Id).Document(db1Doc.Id);
752760

753761
var db1SyncAccumulator = new EventAccumulator<string>(MainThreadId, FailTest);
@@ -793,7 +801,7 @@ Task TestMultiInstanceDocumentReferenceListeners() {
793801
var app1 = db1.App;
794802

795803
var app2 = FirebaseApp.Create(app1.Options, "MultiInstanceDocumentReferenceListenersTest");
796-
var db2 = FirebaseFirestore.GetInstance(app2);
804+
var db2 = TestFirestore(app2);
797805
var db2Doc = db2.Collection(db1Doc.Parent.Id).Document(db1Doc.Id);
798806

799807
var db1DocAccumulator = new EventAccumulator<DocumentSnapshot>(MainThreadId, FailTest);
@@ -827,7 +835,7 @@ Task TestMultiInstanceQueryListeners() {
827835
var app1 = db1.App;
828836

829837
var app2 = FirebaseApp.Create(app1.Options, "MultiInstanceQueryListenersTest");
830-
var db2 = FirebaseFirestore.GetInstance(app2);
838+
var db2 = TestFirestore(app2);
831839
var db2Coll = db2.Collection(db1Coll.Id);
832840

833841
var db1CollAccumulator = new EventAccumulator<QuerySnapshot>(MainThreadId, FailTest);
@@ -1308,7 +1316,7 @@ Task TestTransactionDispose() {
13081316
// Verify that the Transaction is disposed when the Firestore instance terminated.
13091317
{
13101318
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "transaction-terminate");
1311-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
1319+
FirebaseFirestore customDb = TestFirestore(customApp);
13121320
DocumentReference doc = customDb.Document(TestDocument().Path);
13131321
var barrier = new BarrierCompat(2);
13141322
Transaction capturedTransaction = null;
@@ -1343,7 +1351,7 @@ Task TestTransactionDispose() {
13431351
// Verify that the Transaction is disposed when the Firestore instance is disposed.
13441352
{
13451353
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "transaction-dispose1");
1346-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
1354+
FirebaseFirestore customDb = TestFirestore(customApp);
13471355
DocumentReference doc = customDb.Document(TestDocument().Path);
13481356
var barrier = new BarrierCompat(2);
13491357
Transaction capturedTransaction = null;
@@ -1375,7 +1383,7 @@ Task TestTransactionDispose() {
13751383
// directly from the transaction callback.
13761384
{
13771385
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "transaction-dispose2");
1378-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
1386+
FirebaseFirestore customDb = TestFirestore(customApp);
13791387
DocumentReference doc = customDb.Document(TestDocument().Path);
13801388
var barrier = new BarrierCompat(2);
13811389
Transaction capturedTransaction = null;
@@ -1402,7 +1410,7 @@ Task TestTransactionDispose() {
14021410
// from the task returned from the transaction callback.
14031411
{
14041412
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "transaction-dispose3");
1405-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
1413+
FirebaseFirestore customDb = TestFirestore(customApp);
14061414
DocumentReference doc = customDb.Document(TestDocument().Path);
14071415
var barrier = new BarrierCompat(2);
14081416
Transaction capturedTransaction = null;
@@ -1440,7 +1448,7 @@ Task TestTransactionsInParallel() {
14401448
}
14411449
FirebaseFirestore[] firestores = new FirebaseFirestore[apps.Length];
14421450
for (int i = 0; i < firestores.Length; i++) {
1443-
firestores[i] = FirebaseFirestore.GetInstance(apps[i]);
1451+
firestores[i] = TestFirestore(apps[i]);
14441452
}
14451453

14461454
int numTransactionsPerFirestore = 3;
@@ -2453,8 +2461,8 @@ Task TestInQueries() {
24532461

24542462
Task TestDefaultInstanceStable() {
24552463
return Async(() => {
2456-
FirebaseFirestore db1 = FirebaseFirestore.DefaultInstance;
2457-
FirebaseFirestore db2 = FirebaseFirestore.DefaultInstance;
2464+
FirebaseFirestore db1 = db;
2465+
FirebaseFirestore db2 = db;
24582466
AssertEq("FirebaseFirestore.DefaultInstance's not stable", db1, db2);
24592467
});
24602468
}
@@ -2653,7 +2661,7 @@ Task TestTerminate() {
26532661
!docListenerInvoked);
26542662

26552663
// Create a new functional instance.
2656-
var db2 = FirebaseFirestore.GetInstance(app);
2664+
var db2 = TestFirestore(app);
26572665
Assert("Should create a new instance.", db1 != db2);
26582666
AssertTaskSucceeds(db2.DisableNetworkAsync());
26592667
AssertTaskSucceeds(db2.EnableNetworkAsync());
@@ -2679,7 +2687,7 @@ Task TestClearPersistence() {
26792687
// Create a document to use to verify the behavior of ClearPersistenceAsync().
26802688
{
26812689
var app = FirebaseApp.Create(defaultOptions, "TestClearPersistenceApp");
2682-
var db = FirebaseFirestore.GetInstance(app);
2690+
var db = TestFirestore(app);
26832691
var docContents = new Dictionary<string, object> { { "foo", 42 } };
26842692

26852693
var doc = db.Collection("TestCollection").Document();
@@ -2839,12 +2847,12 @@ Task TestObjectMappingSmokeTest() {
28392847
// Verify Firestore instances are singletons.
28402848
Task TestFirestoreSingleton() {
28412849
return Async(() => {
2842-
FirebaseFirestore db2 = FirebaseFirestore.DefaultInstance;
2850+
FirebaseFirestore db2 = db;
28432851
Assert("FirebaseFirestore.DefaultInstance returns a singleton", db == db2);
28442852
Assert("Query.Firestore returns the same instance",
2845-
db == db.Collection("a").WhereEqualTo("x", 1).Firestore);
2853+
db == db.Collection("a").WhereEqualTo("x", 1).Firestore);
28462854
Assert("DocumentReference.Firestore returns the same instance",
2847-
db == db.Document("a/b").Firestore);
2855+
db == db.Document("a/b").Firestore);
28482856
});
28492857
}
28502858

@@ -2853,7 +2861,7 @@ Task TestFirestoreSettings() {
28532861
// Verify that ToString() returns a meaningful value.
28542862
{
28552863
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-tostring-test");
2856-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2864+
FirebaseFirestore customDb = TestFirestore(customApp);
28572865
customDb.Settings.Host = "a.b.c";
28582866
customDb.Settings.SslEnabled = true;
28592867
customDb.Settings.PersistenceEnabled = false;
@@ -2869,9 +2877,15 @@ Task TestFirestoreSettings() {
28692877
// Verify the default FirebaseFirestoreSettings values.
28702878
{
28712879
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-defaults-test");
2872-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2873-
AssertEq(customDb.Settings.Host, "firestore.googleapis.com");
2874-
AssertEq(customDb.Settings.SslEnabled, true);
2880+
FirebaseFirestore customDb = TestFirestore(customApp);
2881+
// While using Firestore Emulator, the host and SslEnabled fields are different from prod.
2882+
if(IsUsingFirestoreEmulator()){
2883+
AssertStringContainsNoCase(customDb.Settings.ToString(), "Host=localhost:");
2884+
AssertEq(customDb.Settings.SslEnabled, false);
2885+
} else {
2886+
AssertEq(customDb.Settings.Host, "firestore.googleapis.com");
2887+
AssertEq(customDb.Settings.SslEnabled, true);
2888+
}
28752889
AssertEq(customDb.Settings.PersistenceEnabled, true);
28762890
AssertEq(customDb.Settings.CacheSizeBytes, 100 * 1024 * 1024);
28772891
customApp.Dispose();
@@ -2880,7 +2894,7 @@ Task TestFirestoreSettings() {
28802894
// Verify that the FirebaseFirestoreSettings written are read back.
28812895
{
28822896
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-readwrite-test");
2883-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2897+
FirebaseFirestore customDb = TestFirestore(customApp);
28842898

28852899
customDb.Settings.Host = "a.b.c";
28862900
AssertEq<string>(customDb.Settings.Host, "a.b.c");
@@ -2908,7 +2922,7 @@ Task TestFirestoreSettings() {
29082922
// Verify the FirebaseFirestoreSettings behavior after the FirebaseFirestore is disposed.
29092923
{
29102924
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-dispose-test");
2911-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2925+
FirebaseFirestore customDb = TestFirestore(customApp);
29122926
FirebaseFirestoreSettings settings = customDb.Settings;
29132927

29142928
var oldHost = settings.Host;
@@ -2937,7 +2951,7 @@ Task TestFirestoreSettings() {
29372951
// Verify the FirebaseFirestoreSettings behavior after the FirebaseFirestore is used.
29382952
{
29392953
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-toolate-test");
2940-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2954+
FirebaseFirestore customDb = TestFirestore(customApp);
29412955
var oldHost = customDb.Settings.Host;
29422956
var oldSslEnabled = customDb.Settings.SslEnabled;
29432957
var oldPersistenceEnabled = customDb.Settings.PersistenceEnabled;
@@ -2964,13 +2978,13 @@ Task TestFirestoreSettings() {
29642978

29652979
customApp.Dispose();
29662980
}
2967-
2981+
29682982
// Verify that FirebaseFirestoreSettings.PersistenceEnabled is respected.
29692983
{
29702984
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "settings-persistence-test");
29712985
string docPath;
29722986
{
2973-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2987+
FirebaseFirestore customDb = TestFirestore(customApp);
29742988
customDb.Settings.PersistenceEnabled = true;
29752989
DocumentReference doc = customDb.Collection("settings-persistence-test").Document();
29762990
docPath = doc.Path;
@@ -2979,7 +2993,7 @@ Task TestFirestoreSettings() {
29792993
AssertTaskSucceeds(customDb.TerminateAsync());
29802994
}
29812995
{
2982-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
2996+
FirebaseFirestore customDb = TestFirestore(customApp);
29832997
customDb.Settings.PersistenceEnabled = false;
29842998
DocumentReference doc = customDb.Document(docPath);
29852999
AssertTaskSucceeds(doc.SetAsync(TestData(1)));
@@ -3288,7 +3302,7 @@ Task TestInternalExceptions() {
32883302

32893303
// Invalid argument.
32903304
try {
3291-
var db1 = FirebaseFirestore.DefaultInstance;
3305+
var db1 = db;
32923306
var db2 = NonDefaultFirestore("InvalidArgument");
32933307

32943308
var batch = db1.StartBatch();
@@ -3304,28 +3318,32 @@ Task TestInternalExceptions() {
33043318
exception, exception is ArgumentException);
33053319
}
33063320

3307-
// Illegal state.
3308-
exception = null;
3309-
try {
3310-
var db = NonDefaultFirestore("IllegalState");
3311-
db.Settings.SslEnabled = false;
3312-
// Make sure the Firestore client is initialized.
3313-
db.Collection("foo").Document("bar");
3321+
// While running against Firestore emulator, the 'sslEnabled' is already set to "false". No exception
3322+
// will be thrown.
3323+
if (!IsUsingFirestoreEmulator()) {
3324+
// Illegal state.
3325+
exception = null;
3326+
try {
3327+
var db = NonDefaultFirestore("IllegalState");
3328+
db.Settings.SslEnabled = false;
3329+
// Make sure the Firestore client is initialized.
3330+
db.Collection("foo").Document("bar");
33143331

3315-
} catch (Exception e) {
3316-
exception = e;
3332+
} catch (Exception e) {
3333+
exception = e;
33173334

3318-
} finally {
3319-
Assert("Expected an exception to be thrown", exception != null);
3320-
Assert("Expected an InvalidOperationException, but received " +
3321-
exception, exception is InvalidOperationException);
3335+
} finally {
3336+
Assert("Expected an exception to be thrown", exception != null);
3337+
Assert("Expected an InvalidOperationException, but received " +
3338+
exception, exception is InvalidOperationException);
3339+
}
33223340
}
33233341

33243342
// Exception in an async method.
33253343
exception = null;
33263344

33273345
{
3328-
var db1 = FirebaseFirestore.DefaultInstance;
3346+
var db1 = db;
33293347
var db2 = NonDefaultFirestore("InternalAssertion");
33303348

33313349
DocumentReference doc1 = db1.Collection("foo").Document("bar");
@@ -3451,10 +3469,10 @@ Task TestLoadBundlesFromOtherProjects_ShouldFail() {
34513469

34523470
Task LoadedBundleDocumentsAlreadyPulledFromBackend_ShouldNotOverwrite() {
34533471
return Async(() => {
3454-
var db = FirebaseFirestore.GetInstance(FirebaseApp.DefaultInstance);
3472+
var db = TestFirestore(FirebaseApp.DefaultInstance);
34553473
Await(db.TerminateAsync());
34563474
Await(db.ClearPersistenceAsync());
3457-
db = FirebaseFirestore.GetInstance(FirebaseApp.DefaultInstance);
3475+
db = TestFirestore(FirebaseApp.DefaultInstance);
34583476

34593477
var collection = db.Collection("coll-1");
34603478
Await(collection.Document("a").SetAsync(new Dictionary<string, object> {
@@ -3784,7 +3802,7 @@ Task TestFirestoreDispose() {
37843802
// Verify that disposing the `FirebaseApp` in turn disposes the `FirebaseFirestore` object.
37853803
{
37863804
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "dispose-app-to-firestore");
3787-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
3805+
FirebaseFirestore customDb = TestFirestore(customApp);
37883806
customApp.Dispose();
37893807
Assert("App property should be null", customDb.App == null);
37903808
}
@@ -3798,7 +3816,7 @@ Task TestFirestoreDispose() {
37983816
Task<string> sampleTypedTask = taskCompletionSource.Task;
37993817

38003818
FirebaseApp customApp = FirebaseApp.Create(db.App.Options, "dispose-exceptions");
3801-
FirebaseFirestore customDb = FirebaseFirestore.GetInstance(customApp);
3819+
FirebaseFirestore customDb = TestFirestore(customApp);
38023820
var doc = customDb.Document("ColA/DocA/ColB/DocB");
38033821
var doc2 = customDb.Document("ColA/DocA/ColB/DocC");
38043822
var collection = doc.Parent;
@@ -4045,7 +4063,7 @@ private FirebaseFirestore NonDefaultFirestore(string appName) {
40454063
// Setting a ProjectId is required (b/158838266).
40464064
appOptions.ProjectId = appName;
40474065
var app = FirebaseApp.Create(appOptions, appName);
4048-
return FirebaseFirestore.GetInstance(app);
4066+
return TestFirestore(app);
40494067
}
40504068

40514069
/// Wraps IEnumerator in an exception handling Task.

0 commit comments

Comments
 (0)