Skip to content

Commit e5733f0

Browse files
committed
fix(e2e): fix FirebaseApp test-isolation race in e2eTest module
1 parent 817b76c commit e5733f0

10 files changed

Lines changed: 74 additions & 160 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.firebase.ui.auth.testutil
2+
3+
import android.content.Context
4+
import com.firebase.ui.auth.FirebaseAuthUI
5+
import com.google.firebase.FirebaseApp
6+
import com.google.firebase.FirebaseOptions
7+
8+
/**
9+
* Returns the "[DEFAULT]" FirebaseApp used by e2e tests, pointed at the local Auth
10+
* emulator, initializing it once per JVM instead of per test.
11+
*
12+
* Every test class used to delete and re-initialize this app in its own `@Before`.
13+
* Robolectric shares statics across test classes within a run, so that churn raced
14+
* with other classes doing the same thing; newer firebase-auth releases surface the
15+
* loser of that race as "FirebaseApp was deleted" from useEmulator(). Per-test
16+
* isolation is already handled by [EmulatorAuthApi.clearEmulatorData], so the app
17+
* itself doesn't need to be recreated for every test.
18+
*/
19+
fun ensureTestFirebaseApp(context: Context): FirebaseApp {
20+
FirebaseApp.getApps(context).firstOrNull { it.name == FirebaseApp.DEFAULT_APP_NAME }?.let {
21+
return it
22+
}
23+
24+
val app = FirebaseApp.initializeApp(
25+
context,
26+
FirebaseOptions.Builder()
27+
.setApiKey("fake-api-key")
28+
.setApplicationId("fake-app-id")
29+
.setProjectId("fake-project-id")
30+
.build()
31+
)
32+
FirebaseAuthUI.getInstance().auth.useEmulator("127.0.0.1", 9099)
33+
return app
34+
}

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringPro
3434
import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
3535
import com.firebase.ui.auth.testutil.EmulatorAuthApi
3636
import com.firebase.ui.auth.testutil.ensureFreshUser
37+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
3738
import com.google.common.truth.Truth.assertThat
38-
import com.google.firebase.FirebaseApp
39-
import com.google.firebase.FirebaseOptions
4039
import org.junit.After
4140
import org.junit.Before
4241
import org.junit.Rule
@@ -68,23 +67,8 @@ class AnonymousAuthScreenTest {
6867

6968
stringProvider = DefaultAuthUIStringProvider(applicationContext)
7069

71-
// Clear any existing Firebase apps
72-
FirebaseApp.getApps(applicationContext).forEach { app ->
73-
app.delete()
74-
}
75-
76-
// Initialize default FirebaseApp
77-
val firebaseApp = FirebaseApp.initializeApp(
78-
applicationContext,
79-
FirebaseOptions.Builder()
80-
.setApiKey("fake-api-key")
81-
.setApplicationId("fake-app-id")
82-
.setProjectId("fake-project-id")
83-
.build()
84-
)
85-
70+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
8671
authUI = FirebaseAuthUI.getInstance()
87-
authUI.auth.useEmulator("127.0.0.1", 9099)
8872

8973
emulatorApi = EmulatorAuthApi(
9074
projectId = firebaseApp.options.projectId
@@ -99,7 +83,10 @@ class AnonymousAuthScreenTest {
9983

10084
@After
10185
fun tearDown() {
102-
// Clean up after each test to prevent test pollution
86+
// Clean up after each test to prevent test pollution. The FirebaseApp itself is
87+
// shared across test classes (see ensureTestFirebaseApp), so the client-side
88+
// session must be reset explicitly here rather than relying on app re-creation.
89+
authUI.auth.signOut()
10390
FirebaseAuthUI.clearInstanceCache()
10491

10592
// Clear emulator data

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/CredentialLinkingScreenTest.kt

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@ import com.firebase.ui.auth.testutil.EmulatorAuthApi
4242
import com.firebase.ui.auth.testutil.awaitWithLooper
4343
import com.firebase.ui.auth.testutil.ensureFreshUser
4444
import com.firebase.ui.auth.testutil.generateMockGoogleIdToken
45+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
4546
import com.firebase.ui.auth.testutil.verifyEmailInEmulator
4647
import com.firebase.ui.auth.util.CountryUtils
4748
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
4849
import com.google.common.truth.Truth.assertThat
49-
import com.google.firebase.FirebaseApp
50-
import com.google.firebase.FirebaseOptions
5150
import kotlinx.coroutines.test.runTest
5251
import org.junit.After
5352
import org.junit.Assume
@@ -89,21 +88,8 @@ class CredentialLinkingScreenTest {
8988
applicationContext = ApplicationProvider.getApplicationContext()
9089
stringProvider = DefaultAuthUIStringProvider(applicationContext)
9190

92-
FirebaseApp.getApps(applicationContext).forEach { app ->
93-
app.delete()
94-
}
95-
96-
val firebaseApp = FirebaseApp.initializeApp(
97-
applicationContext,
98-
FirebaseOptions.Builder()
99-
.setApiKey("fake-api-key")
100-
.setApplicationId("fake-app-id")
101-
.setProjectId("fake-project-id")
102-
.build()
103-
)
104-
91+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
10592
authUI = FirebaseAuthUI.getInstance()
106-
authUI.auth.useEmulator("127.0.0.1", 9099)
10793

10894
authUI.testCredentialManagerProvider = object : AuthProvider.Google.CredentialManagerProvider {
10995
override suspend fun getGoogleCredential(
@@ -137,6 +123,7 @@ class CredentialLinkingScreenTest {
137123

138124
@After
139125
fun tearDown() {
126+
authUI.auth.signOut()
140127
FirebaseAuthUI.clearInstanceCache()
141128
emulatorApi.clearEmulatorData()
142129
}

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
4545
import com.firebase.ui.auth.testutil.EmailLinkTestActivity
4646
import com.firebase.ui.auth.testutil.EmulatorAuthApi
4747
import com.firebase.ui.auth.testutil.ensureFreshUser
48+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
4849
import com.firebase.ui.auth.testutil.verifyEmailInEmulator
4950
import com.google.common.truth.Truth.assertThat
50-
import com.google.firebase.FirebaseApp
51-
import com.google.firebase.FirebaseOptions
5251
import com.google.firebase.auth.actionCodeSettings
5352
import kotlinx.coroutines.runBlocking
5453
import org.junit.After
@@ -97,23 +96,8 @@ class EmailAuthScreenTest {
9796

9897
stringProvider = DefaultAuthUIStringProvider(applicationContext)
9998

100-
// Clear any existing Firebase apps
101-
FirebaseApp.getApps(applicationContext).forEach { app ->
102-
app.delete()
103-
}
104-
105-
// Initialize default FirebaseApp
106-
val firebaseApp = FirebaseApp.initializeApp(
107-
applicationContext,
108-
FirebaseOptions.Builder()
109-
.setApiKey("fake-api-key")
110-
.setApplicationId("fake-app-id")
111-
.setProjectId("fake-project-id")
112-
.build()
113-
)
114-
99+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
115100
authUI = FirebaseAuthUI.getInstance()
116-
authUI.auth.useEmulator("127.0.0.1", 9099)
117101

118102
emulatorApi = EmulatorAuthApi(
119103
projectId = firebaseApp.options.projectId
@@ -138,7 +122,10 @@ class EmailAuthScreenTest {
138122
fun tearDown() {
139123
closeable.close()
140124

141-
// Clean up after each test to prevent test pollution
125+
// Clean up after each test to prevent test pollution. The FirebaseApp itself is
126+
// shared across test classes (see ensureTestFirebaseApp), so the client-side
127+
// session must be reset explicitly here rather than relying on app re-creation.
128+
authUI.auth.signOut()
142129
FirebaseAuthUI.clearInstanceCache()
143130

144131
// Clear emulator data

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,10 @@ import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
3939
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
4040
import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
4141
import com.firebase.ui.auth.testutil.EmulatorAuthApi
42+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
4243
import com.firebase.ui.auth.testutil.generateMockGoogleIdToken
4344
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
4445
import com.google.common.truth.Truth.assertThat
45-
import com.google.firebase.FirebaseApp
46-
import com.google.firebase.FirebaseOptions
4746
import kotlinx.coroutines.test.runTest
4847
import org.junit.After
4948
import org.junit.Before
@@ -85,23 +84,8 @@ class GoogleAuthScreenTest {
8584

8685
stringProvider = DefaultAuthUIStringProvider(applicationContext)
8786

88-
// Clear any existing Firebase apps
89-
FirebaseApp.getApps(applicationContext).forEach { app ->
90-
app.delete()
91-
}
92-
93-
// Initialize default FirebaseApp
94-
val firebaseApp = FirebaseApp.initializeApp(
95-
applicationContext,
96-
FirebaseOptions.Builder()
97-
.setApiKey("fake-api-key")
98-
.setApplicationId("fake-app-id")
99-
.setProjectId("fake-project-id")
100-
.build()
101-
)
102-
87+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
10388
authUI = FirebaseAuthUI.getInstance()
104-
authUI.auth.useEmulator("127.0.0.1", 9099)
10589

10690
val testCredentialManagerProvider = object : AuthProvider.Google.CredentialManagerProvider {
10791
override suspend fun getGoogleCredential(
@@ -140,7 +124,10 @@ class GoogleAuthScreenTest {
140124

141125
@After
142126
fun tearDown() {
143-
// Clean up after each test to prevent test pollution
127+
// Clean up after each test to prevent test pollution. The FirebaseApp itself is
128+
// shared across test classes (see ensureTestFirebaseApp), so the client-side
129+
// session must be reset explicitly here rather than relying on app re-creation.
130+
authUI.auth.signOut()
144131
FirebaseAuthUI.clearInstanceCache()
145132

146133
// Clear emulator data

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/MfaChallengeScreenTest.kt

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
3535
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
3636
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
3737
import com.firebase.ui.auth.mfa.MfaChallengeContentState
38+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
3839
import com.google.common.truth.Truth.assertThat
39-
import com.google.firebase.FirebaseApp
40-
import com.google.firebase.FirebaseOptions
4140
import com.google.firebase.auth.MultiFactorInfo
4241
import com.google.firebase.auth.MultiFactorResolver
4342
import com.google.firebase.auth.MultiFactorSession
@@ -91,23 +90,8 @@ class MfaChallengeScreenTest {
9190
applicationContext = ApplicationProvider.getApplicationContext()
9291
stringProvider = DefaultAuthUIStringProvider(applicationContext)
9392

94-
// Clear any existing Firebase apps
95-
FirebaseApp.getApps(applicationContext).forEach { app ->
96-
app.delete()
97-
}
98-
99-
// Initialize default FirebaseApp
100-
FirebaseApp.initializeApp(
101-
applicationContext,
102-
FirebaseOptions.Builder()
103-
.setApiKey("fake-api-key")
104-
.setApplicationId("fake-app-id")
105-
.setProjectId("fake-project-id")
106-
.build()
107-
)
108-
93+
ensureTestFirebaseApp(applicationContext)
10994
authUI = FirebaseAuthUI.getInstance()
110-
authUI.auth.useEmulator("127.0.0.1", 9099)
11195

11296
// Setup mock resolver
11397
`when`(mockResolver.session).thenReturn(mockSession)

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/MfaDisabledTest.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
2020
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
2121
import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
2222
import com.firebase.ui.auth.testutil.EmulatorAuthApi
23+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
2324
import com.google.common.truth.Truth.assertThat
24-
import com.google.firebase.FirebaseApp
25-
import com.google.firebase.FirebaseOptions
2625
import org.junit.After
2726
import org.junit.Before
2827
import org.junit.Ignore
@@ -58,23 +57,8 @@ class MfaDisabledTest {
5857
applicationContext = ApplicationProvider.getApplicationContext()
5958
stringProvider = DefaultAuthUIStringProvider(applicationContext)
6059

61-
// Clear any existing Firebase apps
62-
FirebaseApp.getApps(applicationContext).forEach { app ->
63-
app.delete()
64-
}
65-
66-
// Initialize default FirebaseApp
67-
val firebaseApp = FirebaseApp.initializeApp(
68-
applicationContext,
69-
FirebaseOptions.Builder()
70-
.setApiKey("fake-api-key")
71-
.setApplicationId("fake-app-id")
72-
.setProjectId("fake-project-id")
73-
.build()
74-
)
75-
60+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
7661
authUI = FirebaseAuthUI.getInstance()
77-
authUI.auth.useEmulator("127.0.0.1", 9099)
7862

7963
emulatorApi = EmulatorAuthApi(
8064
projectId = firebaseApp.options.projectId
@@ -89,7 +73,10 @@ class MfaDisabledTest {
8973

9074
@After
9175
fun tearDown() {
92-
// Clean up after each test to prevent test pollution
76+
// Clean up after each test to prevent test pollution. The FirebaseApp itself is
77+
// shared across test classes (see ensureTestFirebaseApp), so the client-side
78+
// session must be reset explicitly here rather than relying on app re-creation.
79+
authUI.auth.signOut()
9380
FirebaseAuthUI.clearInstanceCache()
9481

9582
// Clear emulator data

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/MfaEnrollmentScreenTest.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ import com.firebase.ui.auth.mfa.MfaEnrollmentContentState
3737
import com.firebase.ui.auth.mfa.MfaEnrollmentStep
3838
import com.firebase.ui.auth.mfa.getHelperText
3939
import com.firebase.ui.auth.mfa.getTitle
40+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
4041
import com.google.common.truth.Truth.assertThat
41-
import com.google.firebase.FirebaseApp
42-
import com.google.firebase.FirebaseOptions
4342
import com.google.firebase.auth.FirebaseUser
4443
import com.google.firebase.auth.MultiFactor
4544
import org.junit.After
@@ -88,21 +87,7 @@ class MfaEnrollmentScreenTest {
8887
applicationContext = ApplicationProvider.getApplicationContext()
8988
stringProvider = DefaultAuthUIStringProvider(applicationContext)
9089

91-
// Clear any existing Firebase apps
92-
FirebaseApp.getApps(applicationContext).forEach { app ->
93-
app.delete()
94-
}
95-
96-
// Initialize default FirebaseApp
97-
FirebaseApp.initializeApp(
98-
applicationContext,
99-
FirebaseOptions.Builder()
100-
.setApiKey("fake-api-key")
101-
.setApplicationId("fake-app-id")
102-
.setProjectId("fake-project-id")
103-
.build()
104-
)
105-
90+
ensureTestFirebaseApp(applicationContext)
10691
authUI = FirebaseAuthUI.getInstance()
10792

10893
// Use mock user instead of real Firebase user

e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/PhoneAuthScreenTest.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,14 @@ import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
3333
import com.firebase.ui.auth.testutil.EmulatorAuthApi
3434
import com.firebase.ui.auth.testutil.awaitWithLooper
3535
import com.firebase.ui.auth.testutil.ensureFreshUser
36+
import com.firebase.ui.auth.testutil.ensureTestFirebaseApp
3637
import com.firebase.ui.auth.testutil.verifyEmailInEmulator
3738
import com.firebase.ui.auth.ui.screens.phone.EnterPhoneNumberUI
3839
import com.firebase.ui.auth.ui.screens.phone.EnterVerificationCodeUI
3940
import com.firebase.ui.auth.ui.screens.phone.PhoneAuthScreen
4041
import com.firebase.ui.auth.ui.screens.phone.PhoneAuthStep
4142
import com.firebase.ui.auth.util.CountryUtils
4243
import com.google.common.truth.Truth.assertThat
43-
import com.google.firebase.FirebaseApp
44-
import com.google.firebase.FirebaseOptions
4544
import com.google.firebase.auth.AuthResult
4645
import org.junit.After
4746
import org.junit.Assume
@@ -75,23 +74,8 @@ class PhoneAuthScreenTest {
7574
applicationContext = ApplicationProvider.getApplicationContext()
7675
stringProvider = DefaultAuthUIStringProvider(applicationContext)
7776

78-
// Clear any existing Firebase apps
79-
FirebaseApp.getApps(applicationContext).forEach { app ->
80-
app.delete()
81-
}
82-
83-
// Initialize default FirebaseApp
84-
val firebaseApp = FirebaseApp.initializeApp(
85-
applicationContext,
86-
FirebaseOptions.Builder()
87-
.setApiKey("fake-api-key")
88-
.setApplicationId("fake-app-id")
89-
.setProjectId("fake-project-id")
90-
.build()
91-
)
92-
77+
val firebaseApp = ensureTestFirebaseApp(applicationContext)
9378
authUI = FirebaseAuthUI.getInstance()
94-
authUI.auth.useEmulator("127.0.0.1", 9099)
9579

9680
emulatorApi = EmulatorAuthApi(
9781
projectId = firebaseApp.options.projectId
@@ -106,7 +90,10 @@ class PhoneAuthScreenTest {
10690

10791
@After
10892
fun tearDown() {
109-
// Clean up after each test to prevent test pollution
93+
// Clean up after each test to prevent test pollution. The FirebaseApp itself is
94+
// shared across test classes (see ensureTestFirebaseApp), so the client-side
95+
// session must be reset explicitly here rather than relying on app re-creation.
96+
authUI.auth.signOut()
11097
FirebaseAuthUI.clearInstanceCache()
11198

11299
// Clear emulator data

0 commit comments

Comments
 (0)