Skip to content

Fix Windows unit test failing because of DataStore threading issue #1542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ dependencies {
kspTest(libs.hilt.compiler)

testImplementation(projects.core.dataTest)
testImplementation(projects.core.datastoreTest)
testImplementation(libs.hilt.android.testing)
testImplementation(projects.sync.syncTest)
testImplementation(libs.kotlin.test)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import javax.inject.Inject
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.R as FeatureForyouR
Expand All @@ -62,24 +60,16 @@ class NavigationTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

/**
* Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission.
*/
@get:Rule(order = 2)
@get:Rule(order = 1)
val postNotificationsPermission = GrantPostNotificationsPermissionRule()

/**
* Use the primary activity to initialize the app normally.
*/
@get:Rule(order = 3)
@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.ui.interests2pane.InterestsListDetailScreen
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
Expand All @@ -40,7 +39,6 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
Expand All @@ -60,11 +58,7 @@ class InterestsListDetailScreenTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
Expand All @@ -47,7 +46,6 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
Expand All @@ -74,18 +72,10 @@ class NiaAppScreenSizesScreenshotTests {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

/**
* Use a test activity to set the content on.
*/
@get:Rule(order = 2)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
Expand All @@ -80,7 +79,6 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
Expand All @@ -107,18 +105,10 @@ class SnackbarInsetsScreenshotTests {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

/**
* Use a test activity to set the content on.
*/
@get:Rule(order = 2)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
Expand All @@ -53,7 +52,6 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
Expand All @@ -80,18 +78,10 @@ class SnackbarScreenshotTests {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

/**
* Use a test activity to set the content on.
*/
@get:Rule(order = 2)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsRes
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.test.InMemoryDataStore
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
Expand All @@ -43,9 +44,7 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals
import kotlin.test.assertTrue

Expand All @@ -67,14 +66,9 @@ class OfflineFirstNewsRepositoryTest {

private lateinit var synchronizer: Synchronizer

@get:Rule
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

@Before
fun setup() {
niaPreferencesDataSource = NiaPreferencesDataSource(
tmpFolder.testUserPreferencesDataStore(testScope.backgroundScope),
)
niaPreferencesDataSource = NiaPreferencesDataSource(InMemoryDataStore(UserPreferences.getDefaultInstance()))
newsResourceDao = TestNewsResourceDao()
topicDao = TestTopicDao()
network = TestNiaNetworkDataSource()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,16 @@ import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.test.InMemoryDataStore
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals

class OfflineFirstTopicsRepositoryTest {
Expand All @@ -52,16 +51,11 @@ class OfflineFirstTopicsRepositoryTest {

private lateinit var synchronizer: Synchronizer

@get:Rule
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

@Before
fun setup() {
topicDao = TestTopicDao()
network = TestNiaNetworkDataSource()
niaPreferences = NiaPreferencesDataSource(
tmpFolder.testUserPreferencesDataStore(testScope.backgroundScope),
)
niaPreferences = NiaPreferencesDataSource(InMemoryDataStore(UserPreferences.getDefaultInstance()))
synchronizer = TestSynchronizer(niaPreferences)

subject = OfflineFirstTopicsRepository(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ package com.google.samples.apps.nowinandroid.core.data.repository

import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.test.InMemoryDataStore
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData
Expand All @@ -28,9 +29,7 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
Expand All @@ -45,14 +44,9 @@ class OfflineFirstUserDataRepositoryTest {

private val analyticsHelper = NoOpAnalyticsHelper()

@get:Rule
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()

@Before
fun setup() {
niaPreferencesDataSource = NiaPreferencesDataSource(
tmpFolder.testUserPreferencesDataStore(testScope.backgroundScope),
)
niaPreferencesDataSource = NiaPreferencesDataSource(InMemoryDataStore(UserPreferences.getDefaultInstance()))

subject = OfflineFirstUserDataRepository(
niaPreferencesDataSource = niaPreferencesDataSource,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.samples.apps.nowinandroid.core.datastore.test

import androidx.datastore.core.DataStore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.updateAndGet

class InMemoryDataStore<T>(initialValue: T) : DataStore<T> {
override val data = MutableStateFlow(initialValue)
override suspend fun updateData(
transform: suspend (it: T) -> T,
) = data.updateAndGet { transform(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,13 @@
package com.google.samples.apps.nowinandroid.core.datastore.test

import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer
import com.google.samples.apps.nowinandroid.core.datastore.di.DataStoreModule
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import kotlinx.coroutines.CoroutineScope
import org.junit.rules.TemporaryFolder
import javax.inject.Singleton

@Module
Expand All @@ -36,26 +32,9 @@ import javax.inject.Singleton
replaces = [DataStoreModule::class],
)
internal object TestDataStoreModule {

@Provides
@Singleton
fun providesUserPreferencesDataStore(
@ApplicationScope scope: CoroutineScope,
userPreferencesSerializer: UserPreferencesSerializer,
tmpFolder: TemporaryFolder,
): DataStore<UserPreferences> =
tmpFolder.testUserPreferencesDataStore(
coroutineScope = scope,
userPreferencesSerializer = userPreferencesSerializer,
)
}

fun TemporaryFolder.testUserPreferencesDataStore(
coroutineScope: CoroutineScope,
userPreferencesSerializer: UserPreferencesSerializer = UserPreferencesSerializer(),
) = DataStoreFactory.create(
serializer = userPreferencesSerializer,
scope = coroutineScope,
) {
newFile("user_preferences_test.pb")
serializer: UserPreferencesSerializer,
): DataStore<UserPreferences> = InMemoryDataStore(serializer.defaultValue)
}
Loading
Loading