app: Make sync workers test use an in memory database instead of a mock
authorDa Risk <da_risk@geekorum.com>
Sun, 05 Jul 2020 18:00:05 -0400
changeset 743 c1c52641dcf7
parent 742 47a7b0b61d5e
child 744 90fd7d86d034
app: Make sync workers test use an in memory database instead of a mock
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorkerTest.kt
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/SendTransactionsWorkerTest.kt
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateAccountInfoWorkerTest.kt
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateArticleStatusWorkerTest.kt
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt
app/src/main/java/com/geekorum/ttrss/article_details/di.kt
app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabaseModule.kt
app/src/main/java/com/geekorum/ttrss/sync/workers/di.kt
manage_feeds/src/main/java/com/geekorum/ttrss/manage_feeds/workers/di.kt
--- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorkerTest.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorkerTest.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -21,6 +21,7 @@
 package com.geekorum.ttrss.sync.workers
 
 import android.accounts.Account
+import android.app.Application
 import android.content.Context
 import androidx.hilt.work.HiltWorkerFactory
 import androidx.test.core.app.ApplicationProvider
@@ -30,13 +31,14 @@
 import com.geekorum.ttrss.core.CoroutineDispatchersProvider
 import com.geekorum.ttrss.data.Article
 import com.geekorum.ttrss.data.ArticleWithAttachments
-import com.geekorum.ttrss.htmlparsers.ImageUrlExtractor
+import com.geekorum.ttrss.data.ArticlesDatabase
+import com.geekorum.ttrss.data.Category
+import com.geekorum.ttrss.data.DiskDatabaseModule
+import com.geekorum.ttrss.data.Feed
 import com.geekorum.ttrss.network.ApiService
-import com.geekorum.ttrss.sync.BackgroundDataUsageManager
-import com.geekorum.ttrss.sync.DatabaseAccessModule
+import com.geekorum.ttrss.network.TinyrssApiModule
 import com.geekorum.ttrss.sync.DatabaseService
 import com.google.common.truth.Truth.assertThat
-import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
@@ -47,30 +49,35 @@
 import dagger.hilt.android.testing.UninstallModules
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineDispatcher
 import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Rule
 import org.junit.Test
 import javax.inject.Inject
+import javax.inject.Singleton
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @HiltAndroidTest
-@UninstallModules(ActualCoroutineDispatchersModule::class, WorkersModule::class, DatabaseAccessModule::class)
+@UninstallModules(ActualCoroutineDispatchersModule::class,
+    WorkersModule::class,
+    TinyrssApiModule::class,
+    DiskDatabaseModule::class)
 class CollectNewArticlesWorkerTest {
     private lateinit var workerBuilder: TestListenableWorkerBuilder<CollectNewArticlesWorker>
     private lateinit var apiService: MockApiService
-    private lateinit var databaseService: MockDatabaseService
-    private lateinit var backgroundDataUsageManager: BackgroundDataUsageManager
 
     private val testCoroutineDispatcher = TestCoroutineDispatcher()
 
     @Inject
     lateinit var hiltWorkerFactory: HiltWorkerFactory
+    @Inject
+    lateinit var databaseService: DatabaseService
 
     @get:Rule
     val hiltRule = HiltAndroidRule(this)
@@ -81,23 +88,17 @@
         io = testCoroutineDispatcher,
         computation = testCoroutineDispatcher)
 
-    @Module(subcomponents = [FakeSyncWorkerComponent::class])
-    @InstallIn(ApplicationComponent::class)
-    abstract class FakeWorkersModule {
-        @Binds
-        abstract fun bindsSyncWorkerComponentBuilder(builder: FakeSyncWorkerComponent.Builder): SyncWorkerComponent.Builder
-    }
-
-    @Module
+    @Module(includes = [FakeSyncWorkersModule::class])
     @InstallIn(ApplicationComponent::class)
     inner class MockModule {
         @Provides
         fun providesApiService(): ApiService = apiService
-        @Provides
-        fun providesDatabaseService(): DatabaseService = databaseService
 
         @Provides
-        fun providesBackgroundDataUsageManager(): BackgroundDataUsageManager = backgroundDataUsageManager
+        @Singleton
+        internal fun providesAppDatabase(application: Application): ArticlesDatabase {
+           return buildInMemoryDatabase(application, dispatchers.io.asExecutor())
+        }
     }
 
 
@@ -107,8 +108,6 @@
         Dispatchers.setMain(testCoroutineDispatcher)
 
         apiService = MyMockApiService()
-        databaseService = MockDatabaseService()
-        backgroundDataUsageManager = MockBackgroundDataUsageManager()
 
         val applicationContext: Context = ApplicationProvider.getApplicationContext()
         workerBuilder = TestListenableWorkerBuilder(applicationContext)
@@ -122,13 +121,16 @@
     }
 
     @Test
-    fun testThatNewArticlesArePresentAfterRunningWorker() = testCoroutineDispatcher.runBlockingTest {
+    fun testThatNewArticlesArePresentAfterRunningWorker() = runBlocking {
+        // prepare database
+        databaseService.insertCategories(listOf(Category(id = 1, title = "Dummy category")))
+        databaseService.insertFeeds(listOf(Feed(id =1 , title= "Dummy feed", catId = 1)))
+        // no articles at the beginning
+        assertThat(databaseService.getArticle(1)).isNull()
+
         val inputData = CollectNewArticlesWorker.getInputData(
                 Account("account", "type"), 1)
 
-        // no articles at the beginning
-        assertThat(databaseService.getArticle(1)).isNull()
-
         workerBuilder.setInputData(inputData)
         val worker = workerBuilder.build()
         val result = worker.doWork()
@@ -141,7 +143,7 @@
 
         override suspend fun getArticles(feedId: Long, sinceId: Long, offset: Int, showExcerpt: Boolean, showContent: Boolean, includeAttachments: Boolean): List<ArticleWithAttachments> {
             return if (offset == 0) {
-                val article = Article(id = 1, isUnread = true)
+                val article = Article(id = 1, isUnread = true, feedId = 1)
                 listOf(ArticleWithAttachments(article, emptyList()))
             } else {
                 emptyList()
--- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/SendTransactionsWorkerTest.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/SendTransactionsWorkerTest.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -20,52 +20,32 @@
  */
 package com.geekorum.ttrss.sync.workers
 
-import android.accounts.Account
-import android.accounts.AccountManager
 import android.app.Application
 import android.content.Context
 import androidx.hilt.work.HiltWorkerFactory
-import androidx.room.Room
-import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.test.core.app.ApplicationProvider
 import androidx.work.ListenableWorker
 import androidx.work.testing.TestListenableWorkerBuilder
 import androidx.work.workDataOf
 import com.geekorum.ttrss.accounts.AndroidTinyrssAccountManager
-import com.geekorum.ttrss.accounts.NetworkLoginModule
-import com.geekorum.ttrss.accounts.PerAccount
-import com.geekorum.ttrss.accounts.ServerInformation
-import com.geekorum.ttrss.accounts.TinyrssAccountTokenRetriever
 import com.geekorum.ttrss.core.ActualCoroutineDispatchersModule
 import com.geekorum.ttrss.core.CoroutineDispatchersProvider
-import com.geekorum.ttrss.data.AccountInfoDao
 import com.geekorum.ttrss.data.Article
-import com.geekorum.ttrss.data.ArticleDao
 import com.geekorum.ttrss.data.ArticlesDatabase
-import com.geekorum.ttrss.data.ArticlesDatabaseModule
-import com.geekorum.ttrss.data.FeedsDao
-import com.geekorum.ttrss.data.SynchronizationDao
+import com.geekorum.ttrss.data.Category
+import com.geekorum.ttrss.data.DiskDatabaseModule
+import com.geekorum.ttrss.data.Feed
 import com.geekorum.ttrss.data.Transaction
 import com.geekorum.ttrss.data.TransactionsDao
-import com.geekorum.ttrss.data.migrations.ALL_MIGRATIONS
 import com.geekorum.ttrss.network.ApiService
 import com.geekorum.ttrss.network.TinyrssApiModule
 import com.geekorum.ttrss.providers.ArticlesContract.Transaction.Field
 import com.geekorum.ttrss.providers.ArticlesContract.Transaction.Field.STARRED
 import com.geekorum.ttrss.providers.ArticlesContract.Transaction.Field.UNREAD
-import com.geekorum.ttrss.providers.PurgeArticlesDao
-import com.geekorum.ttrss.sync.DatabaseAccessModule
 import com.geekorum.ttrss.sync.DatabaseService
-import com.geekorum.ttrss.sync.FeedIconSynchronizer
-import com.geekorum.ttrss.sync.workers.UpdateArticleStatusWorker.Companion.getInputData
-import com.geekorum.ttrss.webapi.LoggedRequestInterceptorFactory
-import com.geekorum.ttrss.webapi.TokenRetriever
 import com.google.common.truth.Truth.assertThat
-import dagger.Binds
-import dagger.BindsInstance
 import dagger.Module
 import dagger.Provides
-import dagger.Subcomponent
 import dagger.hilt.InstallIn
 import dagger.hilt.android.components.ApplicationComponent
 import dagger.hilt.android.testing.BindValue
@@ -74,9 +54,10 @@
 import dagger.hilt.android.testing.UninstallModules
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineDispatcher
 import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Rule
 import org.junit.Test
@@ -87,33 +68,35 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @HiltAndroidTest
-@UninstallModules(ActualCoroutineDispatchersModule::class, WorkersModule::class, DatabaseAccessModule::class)
+@UninstallModules(ActualCoroutineDispatchersModule::class,
+    WorkersModule::class,
+    TinyrssApiModule::class,
+    DiskDatabaseModule::class)
 class SendTransactionsWorkerTest {
     private lateinit var workerBuilder: TestListenableWorkerBuilder<SendTransactionsWorker>
     private lateinit var apiService: MyMockApiService
-    private lateinit var databaseService: MockDatabaseService
     private val testCoroutineDispatcher = TestCoroutineDispatcher()
 
+    @Inject lateinit var databaseService: DatabaseService
+    @Inject lateinit var transactionsDao: TransactionsDao
+
     @JvmField
     @BindValue
     val dispatchers = CoroutineDispatchersProvider(main = testCoroutineDispatcher,
         io = testCoroutineDispatcher,
         computation = testCoroutineDispatcher)
 
-    @Module(subcomponents = [FakeSyncWorkerComponent::class])
-    @InstallIn(ApplicationComponent::class)
-    abstract class FakeWorkersModule {
-        @Binds
-        abstract fun bindsSyncWorkerComponentBuilder(builder: FakeSyncWorkerComponent.Builder): SyncWorkerComponent.Builder
-    }
-
-    @Module
+    @Module(includes = [FakeSyncWorkersModule::class])
     @InstallIn(ApplicationComponent::class)
     inner class MockModule {
         @Provides
         fun providesApiService(): ApiService = apiService
+
         @Provides
-        fun providesDatabaseService(): DatabaseService = databaseService
+        @Singleton
+        internal fun providesAppDatabase(application: Application): ArticlesDatabase {
+            return buildInMemoryDatabase(application, dispatchers.io.asExecutor())
+        }
     }
 
     @Inject
@@ -127,9 +110,8 @@
         hiltRule.inject()
         Dispatchers.setMain(testCoroutineDispatcher)
 
+        apiService = MyMockApiService()
         val applicationContext: Context = ApplicationProvider.getApplicationContext()
-        apiService = MyMockApiService()
-        databaseService = MockDatabaseService()
         workerBuilder = TestListenableWorkerBuilder(applicationContext)
         workerBuilder.setWorkerFactory(hiltWorkerFactory)
     }
@@ -142,14 +124,17 @@
 
 
     @Test
-    fun testTransactionAreSendAndRemovedWhenRunningWorker() = testCoroutineDispatcher.runBlockingTest {
+    fun testTransactionAreSendAndRemovedWhenRunningWorker() = runBlocking {
+        // prepare database
+        databaseService.insertCategories(listOf(Category(id = 1, title = "Dummy category")))
+        databaseService.insertFeeds(listOf(Feed(id =1 , title= "Dummy feed", catId = 1)))
         // insert some transactions On Article 1 and 2
         databaseService.insertArticles(listOf(
-                Article(id = 1, isUnread = false),
-                Article(id = 2, isUnread = true, isStarred = true)))
-        databaseService.insertTransaction(
+                Article(id = 1, isUnread = false, feedId = 1),
+                Article(id = 2, isUnread = true, isStarred = true, feedId = 1)))
+        transactionsDao.insertTransaction(
                 Transaction(id = 1, articleId = 1, field = UNREAD.toString(), value = true))
-        databaseService.insertTransaction(
+        transactionsDao.insertTransaction(
                 Transaction(id = 2, articleId = 2, field = STARRED.toString(), value = false))
         assertThat(databaseService.getTransactions()).hasSize(2)
 
@@ -166,9 +151,9 @@
 
         assertThat(databaseService.getTransactions()).isEmpty()
         val article1 = databaseService.getArticle(1)
-        assertThat(article1).isEqualTo(Article(id = 1, isUnread = true, isTransientUnread = true))
+        assertThat(article1).isEqualTo(Article(id = 1, isUnread = true, isTransientUnread = true, feedId = 1))
         val article2 = databaseService.getArticle(2)
-        assertThat(article2).isEqualTo(Article(id = 2, isUnread = true, isStarred = false))
+        assertThat(article2).isEqualTo(Article(id = 2, isUnread = true, isStarred = false, feedId = 1))
         assertThat(apiService.called).isEqualTo(2)
     }
 
@@ -179,26 +164,3 @@
         }
     }
 }
-
-
-@Subcomponent(modules = [
-    FakeNetworkLoginModule::class
-])
-interface FakeSyncWorkerComponent : SyncWorkerComponent {
-    @Subcomponent.Builder
-    interface Builder : SyncWorkerComponent.Builder
-}
-
-@Module
-object FakeNetworkLoginModule {
-
-    @Provides
-    fun providesServerInformation(accountManager: AndroidTinyrssAccountManager, account: Account): ServerInformation {
-        return object: ServerInformation() {
-            override val apiUrl: String = "https://test.exemple.com/"
-            override val basicHttpAuthUsername: String? = null
-            override val basicHttpAuthPassword: String? = null
-        }
-    }
-
-}
--- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateAccountInfoWorkerTest.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateAccountInfoWorkerTest.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -20,30 +20,24 @@
  */
 package com.geekorum.ttrss.sync.workers
 
-import android.accounts.Account
+import android.app.Application
 import android.content.Context
 import androidx.hilt.work.HiltWorkerFactory
 import androidx.test.core.app.ApplicationProvider
 import androidx.work.ListenableWorker
-import androidx.work.WorkerFactory
-import androidx.work.WorkerParameters
 import androidx.work.testing.TestListenableWorkerBuilder
 import androidx.work.workDataOf
 import com.geekorum.ttrss.accounts.AndroidTinyrssAccountManager
-import com.geekorum.ttrss.accounts.ServerInformation
 import com.geekorum.ttrss.core.ActualCoroutineDispatchersModule
 import com.geekorum.ttrss.core.CoroutineDispatchersProvider
 import com.geekorum.ttrss.data.AccountInfo
-import com.geekorum.ttrss.htmlparsers.ImageUrlExtractor
+import com.geekorum.ttrss.data.ArticlesDatabase
+import com.geekorum.ttrss.data.DiskDatabaseModule
 import com.geekorum.ttrss.network.ApiService
 import com.geekorum.ttrss.network.ServerInfo
-import com.geekorum.ttrss.sync.BackgroundDataUsageManager
-import com.geekorum.ttrss.sync.DatabaseAccessModule
+import com.geekorum.ttrss.network.TinyrssApiModule
 import com.geekorum.ttrss.sync.DatabaseService
-import com.geekorum.ttrss.sync.FeedIconSynchronizer
-import com.geekorum.ttrss.sync.HttpCacher
 import com.google.common.truth.Truth.assertThat
-import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
@@ -54,28 +48,34 @@
 import dagger.hilt.android.testing.UninstallModules
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineDispatcher
 import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Rule
 import org.junit.Test
 import javax.inject.Inject
+import javax.inject.Singleton
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 import com.geekorum.ttrss.data.Account as DataAccount
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @HiltAndroidTest
-@UninstallModules(ActualCoroutineDispatchersModule::class, WorkersModule::class, DatabaseAccessModule::class)
+@UninstallModules(ActualCoroutineDispatchersModule::class,
+    WorkersModule::class,
+    TinyrssApiModule::class,
+    DiskDatabaseModule::class)
 class UpdateAccountInfoWorkerTest {
     private lateinit var workerBuilder: TestListenableWorkerBuilder<UpdateAccountInfoWorker>
     private lateinit var apiService: MyMockApiService
-    private lateinit var databaseService: MockDatabaseService
     private val testCoroutineDispatcher = TestCoroutineDispatcher()
 
     @Inject
     lateinit var hiltWorkerFactory: HiltWorkerFactory
+    @Inject
+    lateinit var databaseService: DatabaseService
 
     @get:Rule
     val hiltRule = HiltAndroidRule(this)
@@ -86,20 +86,17 @@
         io = testCoroutineDispatcher,
         computation = testCoroutineDispatcher)
 
-    @Module(subcomponents = [FakeSyncWorkerComponent::class])
-    @InstallIn(ApplicationComponent::class)
-    abstract class FakeWorkersModule {
-        @Binds
-        abstract fun bindsSyncWorkerComponentBuilder(builder: FakeSyncWorkerComponent.Builder): SyncWorkerComponent.Builder
-    }
-
-    @Module
+    @Module(includes = [FakeSyncWorkersModule::class])
     @InstallIn(ApplicationComponent::class)
     inner class MockModule {
         @Provides
         fun providesApiService(): ApiService = apiService
+
         @Provides
-        fun providesDatabaseService(): DatabaseService = databaseService
+        @Singleton
+        internal fun providesAppDatabase(application: Application): ArticlesDatabase {
+            return buildInMemoryDatabase(application, dispatchers.io.asExecutor())
+        }
     }
 
     @BeforeTest
@@ -107,9 +104,8 @@
         hiltRule.inject()
         Dispatchers.setMain(testCoroutineDispatcher)
 
+        apiService = MyMockApiService()
         val applicationContext: Context = ApplicationProvider.getApplicationContext()
-        apiService = MyMockApiService()
-        databaseService = MockDatabaseService()
         workerBuilder = TestListenableWorkerBuilder(applicationContext)
         workerBuilder.setWorkerFactory(hiltWorkerFactory)
     }
@@ -122,7 +118,7 @@
 
 
     @Test
-    fun testThatAccountInfoAreUpdatedAfterRunningWorker() = testCoroutineDispatcher.runBlockingTest {
+    fun testThatAccountInfoAreUpdatedAfterRunningWorker() = runBlocking {
         // previous accountInfo
         assertThat(databaseService.getAccountInfo("account", "https://test.exemple.com"))
                 .isNull()
--- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateArticleStatusWorkerTest.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/UpdateArticleStatusWorkerTest.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -21,22 +21,24 @@
 package com.geekorum.ttrss.sync.workers
 
 import android.accounts.Account
+import android.app.Application
 import android.content.Context
 import androidx.hilt.work.HiltWorkerFactory
 import androidx.test.core.app.ApplicationProvider
 import androidx.work.ListenableWorker
-import androidx.work.WorkerFactory
-import androidx.work.WorkerParameters
 import androidx.work.testing.TestListenableWorkerBuilder
 import com.geekorum.ttrss.core.ActualCoroutineDispatchersModule
 import com.geekorum.ttrss.core.CoroutineDispatchersProvider
 import com.geekorum.ttrss.data.Article
 import com.geekorum.ttrss.data.ArticleWithAttachments
+import com.geekorum.ttrss.data.ArticlesDatabase
+import com.geekorum.ttrss.data.Category
+import com.geekorum.ttrss.data.DiskDatabaseModule
+import com.geekorum.ttrss.data.Feed
 import com.geekorum.ttrss.network.ApiService
-import com.geekorum.ttrss.sync.DatabaseAccessModule
+import com.geekorum.ttrss.network.TinyrssApiModule
 import com.geekorum.ttrss.sync.DatabaseService
 import com.google.common.truth.Truth.assertThat
-import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
@@ -47,26 +49,33 @@
 import dagger.hilt.android.testing.UninstallModules
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineDispatcher
 import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Rule
 import org.junit.Test
 import javax.inject.Inject
+import javax.inject.Singleton
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @HiltAndroidTest
-@UninstallModules(ActualCoroutineDispatchersModule::class, WorkersModule::class, DatabaseAccessModule::class)
+@UninstallModules(ActualCoroutineDispatchersModule::class,
+    WorkersModule::class,
+    TinyrssApiModule::class,
+    DiskDatabaseModule::class)
 class UpdateArticleStatusWorkerTest {
     private lateinit var workerBuilder: TestListenableWorkerBuilder<UpdateArticleStatusWorker>
     private lateinit var apiService: MyMockApiService
-    private lateinit var databaseService: MockDatabaseService
     private val testCoroutineDispatcher = TestCoroutineDispatcher()
+
     @Inject
     lateinit var hiltWorkerFactory: HiltWorkerFactory
+    @Inject
+    lateinit var databaseService: DatabaseService
 
     @get:Rule
     val hiltRule = HiltAndroidRule(this)
@@ -77,31 +86,26 @@
         io = testCoroutineDispatcher,
         computation = testCoroutineDispatcher)
 
-    @Module(subcomponents = [FakeSyncWorkerComponent::class])
-    @InstallIn(ApplicationComponent::class)
-    abstract class FakeWorkersModule {
-        @Binds
-        abstract fun bindsSyncWorkerComponentBuilder(builder: FakeSyncWorkerComponent.Builder): SyncWorkerComponent.Builder
-    }
-
-    @Module
+    @Module(includes = [FakeSyncWorkersModule::class])
     @InstallIn(ApplicationComponent::class)
     inner class MockModule {
         @Provides
         fun providesApiService(): ApiService = apiService
+
         @Provides
-        fun providesDatabaseService(): DatabaseService = databaseService
+        @Singleton
+        internal fun providesAppDatabase(application: Application): ArticlesDatabase {
+            return buildInMemoryDatabase(application, dispatchers.io.asExecutor())
+        }
     }
 
-
     @BeforeTest
     fun setUp() {
         hiltRule.inject()
         Dispatchers.setMain(testCoroutineDispatcher)
 
+        apiService = MyMockApiService()
         val applicationContext: Context = ApplicationProvider.getApplicationContext()
-        apiService = MyMockApiService()
-        databaseService = MockDatabaseService()
         workerBuilder = TestListenableWorkerBuilder(applicationContext)
         workerBuilder.setWorkerFactory(hiltWorkerFactory)
     }
@@ -114,15 +118,17 @@
 
 
     @Test
-    fun testThatArticleStatusChangeAfterRunningWorker() = testCoroutineDispatcher.runBlockingTest {
-        val inputData = UpdateArticleStatusWorker.getInputData(
-                Account("account", "type"), 1)
-
+    fun testThatArticleStatusChangeAfterRunningWorker() = runBlocking {
+        // prepare database
+        databaseService.insertCategories(listOf(Category(id = 1, title = "Dummy category")))
+        databaseService.insertFeeds(listOf(Feed(id =1 , title= "Dummy feed", catId = 1)))
         // insert a article with status unread to false
-        databaseService.insertArticles(listOf(Article(id = 1, isUnread = false)))
+        databaseService.insertArticles(listOf(Article(id = 1, isUnread = false, feedId = 1)))
         val previous = databaseService.getArticle(1)!!
         assertThat(previous.isUnread).isEqualTo(false)
 
+        val inputData = UpdateArticleStatusWorker.getInputData(
+            Account("account", "type"), 1)
         workerBuilder.setInputData(inputData)
         val worker = workerBuilder.build()
         val result = worker.doWork()
--- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -20,20 +20,27 @@
  */
 package com.geekorum.ttrss.sync.workers
 
-import com.geekorum.ttrss.data.AccountInfo
+import android.accounts.Account
+import android.app.Application
+import androidx.room.Room
+import com.geekorum.ttrss.accounts.AndroidTinyrssAccountManager
+import com.geekorum.ttrss.accounts.ServerInformation
 import com.geekorum.ttrss.data.Article
 import com.geekorum.ttrss.data.ArticleWithAttachments
-import com.geekorum.ttrss.data.ArticlesTags
-import com.geekorum.ttrss.data.Attachment
+import com.geekorum.ttrss.data.ArticlesDatabase
 import com.geekorum.ttrss.data.Category
 import com.geekorum.ttrss.data.Feed
-import com.geekorum.ttrss.data.Metadata
-import com.geekorum.ttrss.data.Transaction
+import com.geekorum.ttrss.data.migrations.ALL_MIGRATIONS
 import com.geekorum.ttrss.network.ApiService
 import com.geekorum.ttrss.network.ServerInfo
 import com.geekorum.ttrss.providers.ArticlesContract
-import com.geekorum.ttrss.sync.BackgroundDataUsageManager
-import com.geekorum.ttrss.sync.DatabaseService
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import dagger.hilt.migration.DisableInstallInCheck
+import java.util.concurrent.Executor
+import java.util.concurrent.Executors
 
 
 internal open class MockApiService : ApiService {
@@ -69,117 +76,51 @@
 
 }
 
-internal open class MockDatabaseService: DatabaseService {
 
-    private var accountInfo: AccountInfo? = null
-
-    private val articles = mutableListOf<Article>()
-    private val attachments = mutableListOf<Attachment>()
-    private val transactions = mutableListOf<Transaction>()
-    private val articlesTags = mutableListOf<ArticlesTags>()
-
-    override suspend fun <R> runInTransaction(block: suspend () -> R) {
-        block()
-    }
-
-    override suspend fun insertFeeds(feeds: List<Feed>) {
-        TODO("not implemented")
-    }
-
-    override suspend fun deleteFeedsAndArticles(feeds: List<Feed>) {
-        TODO("not implemented")
-    }
-
-    override suspend fun getFeeds(): List<Feed> {
-        TODO("not implemented")
-    }
-
-    override suspend fun updateFeedIconUrl(feedId: Long, url: String) {
-        TODO("not implemented")
-    }
-
-    override suspend fun insertCategories(categories: List<Category>) {
-        TODO("not implemented")
-    }
-
-    override suspend fun deleteCategories(categories: List<Category>) {
-        TODO("not implemented")
-    }
-
-    override suspend fun getCategories(): List<Category> {
-        TODO("not implemented")
-    }
-
-    override suspend fun getTransactions(): List<Transaction> {
-        return transactions.toList()
-    }
-
-    override suspend fun deleteTransaction(transaction: Transaction) {
-        transactions.remove(transaction)
-    }
-
-    internal fun insertTransaction(transaction: Transaction) {
-        transactions.add(transaction)
-    }
+@Subcomponent(modules = [
+    FakeNetworkLoginModule::class
+])
+interface FakeSyncWorkerComponent : SyncWorkerComponent {
+    @Subcomponent.Builder
+    interface Builder : SyncWorkerComponent.Builder
+}
 
-    override suspend fun getArticle(id: Long): Article? {
-        return articles.find { it.id == id }
-    }
-
-    override suspend fun getRandomArticleFromFeed(feedId: Long): Article? {
-        TODO("not implemented")
-    }
-
-    override suspend fun insertArticles(articles: List<Article>) {
-        this.articles.addAll(articles)
-    }
-
-    override suspend fun insertArticleTags(articlesTags: List<ArticlesTags>) {
-        this.articlesTags.addAll(articlesTags)
-    }
-
-    override suspend fun updateArticle(article: Article) {
-        val present = articles.first {
-            it.id == article.id
-        }
-        articles.remove(present)
-        articles.add(article)
-    }
+@Module(subcomponents = [FakeSyncWorkerComponent::class])
+@DisableInstallInCheck
+abstract class FakeSyncWorkersModule {
+    @Binds
+    abstract fun bindsSyncWorkerComponentBuilder(builder: FakeSyncWorkerComponent.Builder): SyncWorkerComponent.Builder
+}
 
-    override suspend fun getLatestArticleId(): Long? {
-        return articles.maxBy { it.id }?.id
-    }
-
-    override suspend fun getLatestArticleIdFromFeed(feedId: Long): Long? {
-        return articles.filter { it.feedId == feedId }.maxBy { it.id }?.id
-    }
+@Module
+object FakeNetworkLoginModule {
 
-    override suspend fun updateArticlesMetadata(metadata: List<Metadata>) {
-        metadata.forEach { meta->
-            val present = articles.find { article->
-                article.id == meta.id
-            }
-            articles.remove(present)
-            present?.copy(isUnread= meta.isUnread)?.let {
-                articles.add(it)
-            }
+    @Provides
+    fun providesServerInformation(accountManager: AndroidTinyrssAccountManager, account: Account): ServerInformation {
+        return object : ServerInformation() {
+            override val apiUrl: String = "https://test.exemple.com/"
+            override val basicHttpAuthUsername: String? = null
+            override val basicHttpAuthPassword: String? = null
         }
     }
 
-    override suspend fun getAccountInfo(username: String, apiUrl: String): AccountInfo? {
-        return accountInfo
-    }
-
-    override suspend fun insertAccountInfo(accountInfo: AccountInfo) {
-        this.accountInfo = accountInfo
-    }
-
-    override suspend fun insertAttachments(attachments: List<Attachment>) {
-        this.attachments += attachments
-    }
-
 }
 
-internal open class MockBackgroundDataUsageManager : BackgroundDataUsageManager(null) {
-    override fun canDownloadArticleImages(): Boolean = false
+internal fun buildInMemoryDatabase(application: Application,
+                                   queryExecutor: Executor,
+                                   transactionExecutor: Executor = Executors.newSingleThreadExecutor()
+): ArticlesDatabase {
+    /*
+     * Due to a deadlock in room we need to provide a transactionExecutor running on separate
+     * thread. See
+     * https://medium.com/@eyalg/testing-androidx-room-kotlin-coroutines-2d1faa3e674f
+     * https://github.com/Kotlin/kotlinx.coroutines/pull/1206
+     * https://issuetracker.google.com/issues/135334849
+     */
+    return Room.inMemoryDatabaseBuilder(application, ArticlesDatabase::class.java)
+        .fallbackToDestructiveMigrationOnDowngrade()
+        .setQueryExecutor(queryExecutor)
+        .setTransactionExecutor(transactionExecutor)
+        .addMigrations(*ALL_MIGRATIONS.toTypedArray())
+        .build()
 }
--- a/app/src/main/java/com/geekorum/ttrss/article_details/di.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/main/java/com/geekorum/ttrss/article_details/di.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -29,6 +29,7 @@
 import dagger.hilt.InstallIn
 import dagger.hilt.android.components.ActivityComponent
 import dagger.hilt.android.components.ApplicationComponent
+import dagger.hilt.migration.DisableInstallInCheck
 import dagger.multibindings.IntoMap
 
 
@@ -52,7 +53,7 @@
 }
 
 @Module
-@InstallIn(ApplicationComponent::class)
+@DisableInstallInCheck
 abstract class ResourcesWebFontProviderModule {
 
     @Binds
--- a/app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabaseModule.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabaseModule.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -37,14 +37,6 @@
 @Module
 @InstallIn(ApplicationComponent::class)
 object ArticlesDatabaseModule {
-    @Provides
-    @Singleton
-    internal fun providesAppDatabase(application: Application?): ArticlesDatabase {
-        return Room.databaseBuilder(application!!, ArticlesDatabase::class.java, ArticlesDatabase.DATABASE_NAME)
-                .fallbackToDestructiveMigrationOnDowngrade()
-                .addMigrations(*ALL_MIGRATIONS.toTypedArray())
-                .build()
-    }
 
     @Provides
     internal fun providesRoomDbHelper(database: ArticlesDatabase): SupportSQLiteOpenHelper {
@@ -86,3 +78,17 @@
         return database.manageFeedsDao()
     }
 }
+
+@Module
+@InstallIn(ApplicationComponent::class)
+object DiskDatabaseModule {
+
+    @Provides
+    @Singleton
+    internal fun providesAppDatabase(application: Application?): ArticlesDatabase {
+        return Room.databaseBuilder(application!!, ArticlesDatabase::class.java, ArticlesDatabase.DATABASE_NAME)
+            .fallbackToDestructiveMigrationOnDowngrade()
+            .addMigrations(*ALL_MIGRATIONS.toTypedArray())
+            .build()
+    }
+}
--- a/app/src/main/java/com/geekorum/ttrss/sync/workers/di.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/app/src/main/java/com/geekorum/ttrss/sync/workers/di.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -51,8 +51,7 @@
 abstract class WorkersModule
 
 @Subcomponent(modules = [
-    NetworkLoginModule::class,
-    TinyrssApiModule::class
+    NetworkLoginModule::class
 ])
 @PerAccount
 interface SyncWorkerComponent {
--- a/manage_feeds/src/main/java/com/geekorum/ttrss/manage_feeds/workers/di.kt	Sun Jul 05 00:31:10 2020 -0400
+++ b/manage_feeds/src/main/java/com/geekorum/ttrss/manage_feeds/workers/di.kt	Sun Jul 05 18:00:05 2020 -0400
@@ -72,8 +72,7 @@
 
 @Module(includes = [
     TinyrssApiModule::class,
-    NetworkLoginModule::class,
-    AndroidTinyrssAccountManagerModule::class
+    NetworkLoginModule::class
 ])
 internal class ApiServiceModule {