Skip to content

Commit 2fa97f4

Browse files
authored
Move Android test as jvm test: TabDataRepository, and fix flaky test (#5316)
Task/Issue URL: https://app.asana.com/0/1202552961248957/1208839731155341/f ### Description Fixes flaky test due time reference. Moves it as jvm. ### Steps to test this PR _Feature 1_ - [ ] CI is green - [ ] ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)|
1 parent 808b6a0 commit 2fa97f4

File tree

12 files changed

+97
-46
lines changed

12 files changed

+97
-46
lines changed

app/src/androidTest/java/com/duckduckgo/app/TestExtensions.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,9 @@
1616

1717
package com.duckduckgo.app
1818

19-
import androidx.annotation.UiThread
20-
import androidx.lifecycle.LiveData
21-
import androidx.lifecycle.Observer
2219
import androidx.test.platform.app.InstrumentationRegistry
2320
import com.duckduckgo.app.di.AppComponent
2421
import com.duckduckgo.app.global.DuckDuckGoApplication
25-
import java.util.concurrent.CountDownLatch
26-
import java.util.concurrent.TimeUnit
27-
28-
// from https://stackoverflow.com/a/44991770/73479
29-
@UiThread
30-
fun <T> LiveData<T>.blockingObserve(): T? {
31-
var value: T? = null
32-
val latch = CountDownLatch(1)
33-
val innerObserver = Observer<T> {
34-
value = it
35-
latch.countDown()
36-
}
37-
observeForever(innerObserver)
38-
latch.await(2, TimeUnit.SECONDS)
39-
return value
40-
}
4122

4223
fun getApp(): DuckDuckGoApplication {
4324
return InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as DuckDuckGoApplication

app/src/androidTest/java/com/duckduckgo/app/browser/logindetection/BrowserTabFireproofDialogsEventHandlerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package com.duckduckgo.app.browser.logindetection
1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
2121
import androidx.test.platform.app.InstrumentationRegistry
22-
import com.duckduckgo.app.blockingObserve
2322
import com.duckduckgo.app.browser.logindetection.FireproofDialogsEventHandler.Event
2423
import com.duckduckgo.app.browser.logindetection.FireproofDialogsEventHandler.Event.FireproofWebSiteSuccess
2524
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
@@ -33,6 +32,7 @@ import com.duckduckgo.app.pixels.AppPixelName
3332
import com.duckduckgo.app.settings.db.SettingsDataStore
3433
import com.duckduckgo.app.statistics.pixels.Pixel
3534
import com.duckduckgo.common.test.CoroutineTestRule
35+
import com.duckduckgo.common.test.blockingObserve
3636
import kotlinx.coroutines.test.runTest
3737
import org.junit.After
3838
import org.junit.Assert.assertNull

app/src/androidTest/java/com/duckduckgo/app/fire/fireproofwebsite/data/FireproofWebsiteRepositoryImplTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package com.duckduckgo.app.fire.fireproofwebsite.data
1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
2121
import androidx.test.platform.app.InstrumentationRegistry
22-
import com.duckduckgo.app.blockingObserve
2322
import com.duckduckgo.app.browser.favicon.FaviconManager
2423
import com.duckduckgo.app.global.db.AppDatabase
2524
import com.duckduckgo.common.test.CoroutineTestRule
25+
import com.duckduckgo.common.test.blockingObserve
2626
import dagger.Lazy
2727
import kotlinx.coroutines.test.runTest
2828
import org.junit.After

app/src/androidTest/java/com/duckduckgo/app/location/data/LocationPermissionsRepositoryImplTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package com.duckduckgo.app.location.data
1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
2121
import androidx.test.platform.app.InstrumentationRegistry
22-
import com.duckduckgo.app.blockingObserve
2322
import com.duckduckgo.app.browser.favicon.FaviconManager
2423
import com.duckduckgo.app.global.db.AppDatabase
2524
import com.duckduckgo.common.test.CoroutineTestRule
25+
import com.duckduckgo.common.test.blockingObserve
2626
import dagger.Lazy
2727
import kotlinx.coroutines.test.runTest
2828
import org.junit.After

app/src/androidTest/java/com/duckduckgo/app/privacy/db/NetworkLeaderboardDaoTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ package com.duckduckgo.app.privacy.db
1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
2121
import androidx.test.platform.app.InstrumentationRegistry
22-
import com.duckduckgo.app.blockingObserve
2322
import com.duckduckgo.app.global.db.AppDatabase
23+
import com.duckduckgo.common.test.blockingObserve
2424
import org.junit.After
2525
import org.junit.Assert.assertEquals
2626
import org.junit.Assert.assertTrue

app/src/androidTest/java/com/duckduckgo/app/privacy/db/UserAllowListDaoTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ package com.duckduckgo.app.privacy.db
1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
2121
import androidx.test.platform.app.InstrumentationRegistry
22-
import com.duckduckgo.app.blockingObserve
2322
import com.duckduckgo.app.global.db.AppDatabase
2423
import com.duckduckgo.common.test.CoroutineTestRule
24+
import com.duckduckgo.common.test.blockingObserve
2525
import kotlinx.coroutines.flow.first
2626
import kotlinx.coroutines.runBlocking
2727
import org.junit.After

app/src/main/java/com/duckduckgo/app/tabs/db/TabsDao.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import com.duckduckgo.common.utils.swap
2424
import com.duckduckgo.di.scopes.AppScope
2525
import dagger.SingleInstanceIn
2626
import java.time.LocalDateTime
27-
import java.time.ZoneOffset
2827
import kotlinx.coroutines.flow.Flow
2928

3029
@Dao
@@ -170,7 +169,7 @@ abstract class TabsDao {
170169
@Query("update tabs set lastAccessTime=:lastAccessTime where tabId=:tabId")
171170
abstract fun updateTabLastAccess(
172171
tabId: String,
173-
lastAccessTime: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
172+
lastAccessTime: LocalDateTime,
174173
)
175174

176175
@Query("update tabs set url=:url, title=:title, viewed=:viewed where tabId=:tabId")

app/src/main/java/com/duckduckgo/app/tabs/model/TabDataRepository.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType
3030
import com.duckduckgo.app.tabs.model.TabSwitcherData.UserState
3131
import com.duckduckgo.app.tabs.store.TabSwitcherDataStore
3232
import com.duckduckgo.common.utils.ConflatedJob
33+
import com.duckduckgo.common.utils.CurrentTimeProvider
3334
import com.duckduckgo.common.utils.DispatcherProvider
3435
import com.duckduckgo.di.scopes.AppScope
3536
import dagger.SingleInstanceIn
3637
import io.reactivex.Scheduler
3738
import io.reactivex.schedulers.Schedulers
38-
import java.time.LocalDateTime
39-
import java.time.ZoneOffset
4039
import java.util.UUID
4140
import javax.inject.Inject
4241
import kotlinx.coroutines.CoroutineScope
@@ -57,6 +56,7 @@ class TabDataRepository @Inject constructor(
5756
private val webViewPreviewPersister: WebViewPreviewPersister,
5857
private val faviconManager: FaviconManager,
5958
private val tabSwitcherDataStore: TabSwitcherDataStore,
59+
private val timeProvider: CurrentTimeProvider,
6060
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
6161
private val dispatchers: DispatcherProvider,
6262
) : TabRepository {
@@ -199,7 +199,7 @@ class TabDataRepository @Inject constructor(
199199
}
200200

201201
override fun countTabsAccessedWithinRange(accessOlderThan: Long, accessNotMoreThan: Long?): Int {
202-
val now = LocalDateTime.now(ZoneOffset.UTC)
202+
val now = timeProvider.localDateTimeNow()
203203
val start = now.minusDays(accessOlderThan)
204204
val end = accessNotMoreThan?.let { now.minusDays(it).minusSeconds(1) } // subtracted a second to make the end limit inclusive
205205
return tabsDao.tabs().filter {
@@ -245,7 +245,7 @@ class TabDataRepository @Inject constructor(
245245

246246
override suspend fun updateTabLastAccess(tabId: String) {
247247
databaseExecutor().scheduleDirect {
248-
tabsDao.updateTabLastAccess(tabId)
248+
tabsDao.updateTabLastAccess(tabId, timeProvider.localDateTimeNow())
249249
}
250250
}
251251

app/src/androidTest/java/com/duckduckgo/app/tabs/db/TabsDaoTest.kt renamed to app/src/test/java/com/duckduckgo/tabs/db/TabsDaoTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 DuckDuckGo
2+
* Copyright (c) 2024 DuckDuckGo
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,12 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.duckduckgo.app.tabs.db
17+
package com.duckduckgo.tabs.db
1818

1919
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2020
import androidx.room.Room
21+
import androidx.test.ext.junit.runners.AndroidJUnit4
2122
import androidx.test.platform.app.InstrumentationRegistry
2223
import com.duckduckgo.app.global.db.AppDatabase
24+
import com.duckduckgo.app.tabs.db.TabsDao
2325
import com.duckduckgo.app.tabs.model.TabEntity
2426
import com.duckduckgo.app.tabs.model.TabSelectionEntity
2527
import kotlinx.coroutines.test.runTest
@@ -28,7 +30,9 @@ import org.junit.Assert.*
2830
import org.junit.Before
2931
import org.junit.Rule
3032
import org.junit.Test
33+
import org.junit.runner.RunWith
3134

35+
@RunWith(AndroidJUnit4::class)
3236
class TabsDaoTest {
3337

3438
@get:Rule

app/src/androidTest/java/com/duckduckgo/app/tabs/model/TabDataRepositoryTest.kt renamed to app/src/test/java/com/duckduckgo/tabs/model/TabDataRepositoryTest.kt

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 DuckDuckGo
2+
* Copyright (c) 2024 DuckDuckGo
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,15 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
@file:Suppress("RemoveExplicitTypeArguments")
18-
19-
package com.duckduckgo.app.tabs.model
17+
package com.duckduckgo.tabs.model
2018

2119
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
2220
import androidx.lifecycle.MutableLiveData
2321
import androidx.room.Room
22+
import androidx.test.ext.junit.runners.AndroidJUnit4
2423
import androidx.test.platform.app.InstrumentationRegistry
25-
import com.duckduckgo.app.blockingObserve
2624
import com.duckduckgo.app.browser.DuckDuckGoUrlDetector
2725
import com.duckduckgo.app.browser.certificates.BypassedSSLCertificatesRepository
2826
import com.duckduckgo.app.browser.favicon.FaviconManager
@@ -31,24 +29,42 @@ import com.duckduckgo.app.global.db.AppDatabase
3129
import com.duckduckgo.app.global.model.SiteFactoryImpl
3230
import com.duckduckgo.app.privacy.db.UserAllowListRepository
3331
import com.duckduckgo.app.tabs.db.TabsDao
32+
import com.duckduckgo.app.tabs.model.TabDataRepository
33+
import com.duckduckgo.app.tabs.model.TabEntity
34+
import com.duckduckgo.app.tabs.model.TabSelectionEntity
3435
import com.duckduckgo.app.tabs.store.TabSwitcherDataStore
3536
import com.duckduckgo.app.trackerdetection.EntityLookup
3637
import com.duckduckgo.common.test.CoroutineTestRule
3738
import com.duckduckgo.common.test.InstantSchedulersRule
39+
import com.duckduckgo.common.test.blockingObserve
40+
import com.duckduckgo.common.utils.CurrentTimeProvider
3841
import com.duckduckgo.duckplayer.api.DuckPlayer
3942
import com.duckduckgo.privacy.config.api.ContentBlocking
43+
import java.time.Instant
4044
import java.time.LocalDateTime
4145
import java.time.ZoneOffset
4246
import kotlinx.coroutines.channels.Channel
4347
import kotlinx.coroutines.flow.consumeAsFlow
4448
import kotlinx.coroutines.launch
4549
import kotlinx.coroutines.test.runTest
4650
import org.junit.After
47-
import org.junit.Assert.*
51+
import org.junit.Assert.assertEquals
52+
import org.junit.Assert.assertFalse
53+
import org.junit.Assert.assertNotNull
54+
import org.junit.Assert.assertNotSame
55+
import org.junit.Assert.assertTrue
4856
import org.junit.Rule
4957
import org.junit.Test
50-
import org.mockito.kotlin.*
51-
58+
import org.junit.runner.RunWith
59+
import org.mockito.kotlin.any
60+
import org.mockito.kotlin.anyOrNull
61+
import org.mockito.kotlin.argumentCaptor
62+
import org.mockito.kotlin.eq
63+
import org.mockito.kotlin.mock
64+
import org.mockito.kotlin.verify
65+
import org.mockito.kotlin.whenever
66+
67+
@RunWith(AndroidJUnit4::class)
5268
class TabDataRepositoryTest {
5369

5470
@get:Rule
@@ -206,6 +222,7 @@ class TabDataRepositoryTest {
206222
val existingTabs = listOf(tab0)
207223

208224
whenever(mockDao.tabs()).thenReturn(existingTabs)
225+
whenever(mockDao.lastTab()).thenReturn(existingTabs.last())
209226

210227
testee.add("http://www.example.com")
211228

@@ -468,7 +485,7 @@ class TabDataRepositoryTest {
468485
@Test
469486
fun getActiveTabCountReturnsCorrectCountWhenTabsYoungerThanSpecifiedDay() = runTest {
470487
// Arrange: No tabs in the repository
471-
val now = LocalDateTime.now(ZoneOffset.UTC)
488+
val now = now()
472489
val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(6))
473490
val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(8))
474491
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(10))
@@ -497,10 +514,10 @@ class TabDataRepositoryTest {
497514
@Test
498515
fun getInactiveTabCountReturnsCorrectCountWhenAllTabsOlderThanSpecifiedDay() = runTest {
499516
// Arrange: Add some tabs with different last access times
500-
val now = LocalDateTime.now(ZoneOffset.UTC)
517+
val now = now()
501518
val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(8))
502519
val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10))
503-
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(9))
520+
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(9).minusSeconds(1))
504521
val tab4 = TabEntity(tabId = "tab4")
505522
whenever(mockDao.tabs()).thenReturn(listOf(tab1, tab2, tab3, tab4))
506523
val testee = tabDataRepository()
@@ -514,7 +531,7 @@ class TabDataRepositoryTest {
514531
@Test
515532
fun getInactiveTabCountReturnsCorrectCountWhenAllTabsInactiveWithinRange() = runTest {
516533
// Arrange: Add some tabs with different last access times
517-
val now = LocalDateTime.now(ZoneOffset.UTC)
534+
val now = now()
518535
val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(8))
519536
val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10))
520537
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(9))
@@ -531,7 +548,7 @@ class TabDataRepositoryTest {
531548
@Test
532549
fun getInactiveTabCountReturnsZeroWhenNoTabsInactiveWithinRange() = runTest {
533550
// Arrange: Add some tabs with different last access times
534-
val now = LocalDateTime.now(ZoneOffset.UTC)
551+
val now = now()
535552
val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(5))
536553
val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(6))
537554
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(13))
@@ -548,7 +565,7 @@ class TabDataRepositoryTest {
548565
@Test
549566
fun getInactiveTabCountReturnsCorrectCountWhenSomeTabsInactiveWithinRange() = runTest {
550567
// Arrange: Add some tabs with different last access times
551-
val now = LocalDateTime.now(ZoneOffset.UTC)
568+
val now = now()
552569
val tab1 = TabEntity(tabId = "tab1", lastAccessTime = now.minusDays(5))
553570
val tab2 = TabEntity(tabId = "tab2", lastAccessTime = now.minusDays(10))
554571
val tab3 = TabEntity(tabId = "tab3", lastAccessTime = now.minusDays(15))
@@ -572,6 +589,7 @@ class TabDataRepositoryTest {
572589
faviconManager: FaviconManager = mock(),
573590
tabSwitcherDataStore: TabSwitcherDataStore = mock(),
574591
duckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock(),
592+
timeProvider: CurrentTimeProvider = FakeTimeProvider(),
575593
): TabDataRepository {
576594
return TabDataRepository(
577595
dao,
@@ -588,6 +606,7 @@ class TabDataRepositoryTest {
588606
webViewPreviewPersister,
589607
faviconManager,
590608
tabSwitcherDataStore,
609+
timeProvider,
591610
coroutinesTestRule.testScope,
592611
coroutinesTestRule.testDispatcherProvider,
593612
)
@@ -608,7 +627,19 @@ class TabDataRepositoryTest {
608627
return mockDao
609628
}
610629

630+
private fun now(): LocalDateTime {
631+
return FakeTimeProvider().localDateTimeNow()
632+
}
633+
611634
companion object {
612635
const val TAB_ID = "abcd"
613636
}
637+
638+
private class FakeTimeProvider : CurrentTimeProvider {
639+
var currentTime: Instant = Instant.parse("2024-10-16T00:00:00.00Z")
640+
641+
override fun currentTimeMillis(): Long = currentTime.toEpochMilli()
642+
override fun elapsedRealtime(): Long = throw UnsupportedOperationException()
643+
override fun localDateTimeNow(): LocalDateTime = currentTime.atZone(ZoneOffset.UTC).toLocalDateTime()
644+
}
614645
}

0 commit comments

Comments
 (0)