Repository Unit Tests #50

Merged
AmineB merged 38 commits from davidoskky/ReaderForSelfoss-multiplatform:repository_tests into master 2022-09-30 11:31:55 +00:00
2 changed files with 120 additions and 2 deletions
Showing only changes of commit 758708e18d - Show all commits

View File

@ -380,11 +380,13 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun deleteDBAction(action: ACTION) =
db.actionsQueries.deleteAction(action.id)
// TODO: This function should be private
fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
// TODO: This function should be private
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
db.tagsQueries.deleteAllTags()
db.tagsQueries.transaction {
@ -394,7 +396,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
}
}
fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
db.sourcesQueries.deleteAllSources()
db.sourcesQueries.transaction {

View File

@ -2,16 +2,19 @@ package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.dao.TAG
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.toEntity
import com.github.ln_12.library.ConnectivityStatus
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertNotSame
import kotlin.test.assertSame
class RepositoryTest() {
@ -30,6 +33,7 @@ class RepositoryTest() {
every { appSettingsService.getApiVersion() } returns 4
every { appSettingsService.getBaseUrl() } returns "https://test.com/selfoss/"
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(true)
@ -37,6 +41,9 @@ class RepositoryTest() {
coEvery { api.stats() } returns SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED)
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
every { db.tagsQueries.deleteAllTags() } returns Unit
every { db.tagsQueries.transaction(any(), any()) } returns Unit
every { db.tagsQueries.insertTag(any()) } returns Unit
}
AmineB marked this conversation as resolved
Review

This does not seem to do anything. Is it needed ?

This does not seem to do anything. Is it needed ?
Review

The test can fail if the repository initialization generates an error; this is just a very general case to identify if something in the repository initialization is broken.
All other tests would fail as well, but this will give you certainty that the point of failure is in the initialization.

The test can fail if the repository initialization generates an error; this is just a very general case to identify if something in the repository initialization is broken. All other tests would fail as well, but this will give you certainty that the point of failure is in the initialization.
Review

The way I understand your comment is that sometimes, the repository can fail an throw an error.

This should not be handled in a unit test, but handled in the code.

The exception should be catched and handled there.

You can keep your tests, but the call should return something, and the tests should actually test something.

Same for the three following tests.

The way I understand your comment is that sometimes, the repository can fail an throw an error. This should not be handled in a unit test, but handled in the code. The exception should be catched and handled there. You can keep your tests, but the call should return something, and the tests should actually **test** something. Same for the three following tests.
@Test
AmineB marked this conversation as resolved Outdated

As mentionnend in my previous comment. The try catch must be in the code, not in the unit test.

As mentionnend in my previous comment. The try catch must be in the code, not in the unit test.

Same for all the other try catch in here.

Same for all the other try catch in here.

I'm not sure how I would implement this test otherwise, I guess the repository should raise an exception during initialization then.

I'm not sure how I would implement this test otherwise, I guess the repository should raise an exception during initialization then.

I guess the repository should raise an exception during initialization then.

Yep. That should be the case.

> I guess the repository should raise an exception during initialization then. Yep. That should be the case.
@ -281,6 +288,115 @@ class RepositoryTest() {
coVerify(exactly = 0) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList()}
}
AmineB marked this conversation as resolved
Review

Be cause of the line 40 of the setup function you have an unread/starred item in your DB.

Be cause of the line 40 of the `setup` function you have an unread/starred item in your DB.
Review

Yes, this is intended.
Since items caching is disabled I would expect the function not to access items in the database.
Maybe it's better if I define that item within this function to avoid ambiguity.

Yes, this is intended. Since items caching is disabled I would expect the function not to access items in the database. Maybe it's better if I define that item within this function to avoid ambiguity.
Review

Since items caching is disabled I would expect the function not to access items in the database.

I answerd this in another comment.

The DB should be empty when the setting is disabled, so this should never happen.

> Since items caching is disabled I would expect the function not to access items in the database. I answerd this in another comment. The DB should be empty when the setting is disabled, so this should never happen.
Review

Even if the DB is empty, there is no reason to call it if the setting is disabled.
I placed something in the DB just to test that it was not called.

Even if the DB is empty, there is no reason to call it if the setting is disabled. I placed something in the DB just to test that it was not called.
Review

I find it useless to test if caching is enabled to check if there is something in the DB.

I find it useless to test if caching is enabled to check if there is something in the DB.
Review

It should be quicker and prevents errors in case some problems with DB clearing happens.

It should be quicker and prevents errors in case some problems with DB clearing happens.
Review

Still, db cleaning should be done on setting change.

Still, db cleaning should be done on setting change.
@Test
fun `Get tags`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
coEvery { api.tags() } returns tags
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
assertSame(tags, testTags)
verify(exactly = 0) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags with sources update disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns tags
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
assertNotSame(tags, testTags)
assertSame(tagsDB.first().name, testTags?.first()?.tag)
coVerify(exactly = 0) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
AmineB marked this conversation as resolved
Review

I don't understand what you wanted to test here. The network is available, so the two will be the same.

I don't understand what you wanted to test here. The network is available, so the two will be the same.
Review

If update_sources handles tags fetching, then tags should not be fetched using the api.

If update_sources handles tags fetching, then tags should not be fetched using the api.
Review

The network is available. The api call will be done. update_sources handles the tags/sources caching.

The network is available. The api call will be done. `update_sources` handles the tags/sources caching.
Review

I am confused, as far as I remember update_sources was introduced to limit the amount of api calls to selfoss because that was heavy on the database and created some problems.
That is also how it is described in the option and that is what I would expect it to do.
Thus disabling update_sources should prevent such api calls.

I would use items_caching to enable/disable the use of the local database, without distinguishing between sources, tags and items because it makes no sense to store items and not tags or to store just tags and not items.
Also items_caching could be renamed to make it more clear, maybe enable_database.

I am confused, as far as I remember update_sources was introduced to limit the amount of api calls to selfoss because that was heavy on the database and created some problems. That is also how it is described in the option and that is what I would expect it to do. Thus disabling update_sources should prevent such api calls. I would use items_caching to enable/disable the use of the local database, without distinguishing between sources, tags and items because it makes no sense to store items and not tags or to store just tags and not items. Also items_caching could be renamed to make it more clear, maybe enable_database.
Review

I am confused, as far as I remember update_sources was introduced to limit the amount of api calls to selfoss because that was heavy on the database and created some problems.
That is also how it is described in the option and that is what I would expect it to do.
Thus disabling update_sources should prevent such api calls.

With update_sources enabled, the api calls to tags and sources is always done.

With update_sources disabled, the api calls to tags and sources is done once to cache the data, and then, the data in the cache is used.

I would use items_caching to enable/disable the use of the local database, without distinguishing between sources, tags and items because it makes no sense to store items and not tags or to store just tags and not items.

Users should be able to store only tags and sources, but items_caching should cache everything, you are right.

Also items_caching could be renamed to make it more clear, maybe enable_database.

I don't like renaming settings names, because it means having "migration code" that handle setting the value from the old key. This code could be useless, and we don't really know when to delete it, because users may not update their app every release.

> I am confused, as far as I remember update_sources was introduced to limit the amount of api calls to selfoss because that was heavy on the database and created some problems. That is also how it is described in the option and that is what I would expect it to do. Thus disabling update_sources should prevent such api calls. With `update_sources` enabled, the api calls to tags and sources is always done. With `update_sources` disabled, the api calls to tags and sources is done once to cache the data, and then, the data in the cache is used. > I would use items_caching to enable/disable the use of the local database, without distinguishing between sources, tags and items because it makes no sense to store items and not tags or to store just tags and not items. Users should be able to store only tags and sources, but `items_caching` should cache everything, you are right. > Also items_caching could be renamed to make it more clear, maybe enable_database. I don't like renaming settings names, because it means having "migration code" that handle setting the value from the old key. This code could be useless, and we don't really know when to delete it, because users may not update their app every release.
Review

With update_sources disabled, the api calls to tags and sources is done once to cache the data, and then, the data in the cache is used.

Alright, so I should verify that the call to the api is performed only once, regardless of how many times this function is called and that tags and sources are correctly stored in the db.

Users should be able to store only tags and sources, but items_caching should cache everything, you are right.

If I correctly undestood: tags and sources are always saved to the db and it is not possible to disable this behavior, items are stored in the db only if items_caching is enabled.

> With update_sources disabled, the api calls to tags and sources is done once to cache the data, and then, the data in the cache is used. Alright, so I should verify that the call to the api is performed only once, regardless of how many times this function is called and that tags and sources are correctly stored in the db. > Users should be able to store only tags and sources, but items_caching should cache everything, you are right. If I correctly undestood: tags and sources are always saved to the db and it is not possible to disable this behavior, items are stored in the db only if items_caching is enabled.
Review

Alright, so I should verify that the call to the api is performed only once, regardless of how many times this function is called and that tags and sources are correctly stored in the db.

Yep, you could do that.

If I correctly undestood: tags and sources are always saved to the db and it is not possible to disable this behavior, items are stored in the db only if items_caching is enabled.

No.

Tags and sources should be saved to the db when any of update_sources (this is implemented) or items_caching (this is not) is enabled.

Items are stored only if items_caching is enabled.

> Alright, so I should verify that the call to the api is performed only once, regardless of how many times this function is called and that tags and sources are correctly stored in the db. Yep, you could do that. > If I correctly undestood: tags and sources are always saved to the db and it is not possible to disable this behavior, items are stored in the db only if items_caching is enabled. No. Tags and sources should be saved to the db when any of update_sources (this is implemented) or items_caching (this is not) is enabled. Items are stored only if items_caching is enabled.
@Test
AmineB marked this conversation as resolved Outdated

Same here, the two values are different in the initialization, they couldn't be the same after.

Same here, the two values are different in the initialization, they couldn't be the same after.

I would expect tags to be fetched from the database, since the api should not be used.

I would expect tags to be fetched from the database, since the api should not be used.

The network is available. The api call will be done. update_sources handles the tags/sources caching.

The network is available. The api call will be done. update_sources handles the tags/sources caching.
fun `Get tags with sources update and db disabled`() {
AmineB marked this conversation as resolved
Review

The network is available, so this will be called. And the next one won't.

The network is available, so this will be called. And the next one won't.
Review

If update_sources handles tag fetching then it shouldn't.
Maybe it would be better to rename update_sources so that it becomes more clear that it handles tags as well?
I do get confused at times.

If update_sources handles tag fetching then it shouldn't. Maybe it would be better to rename update_sources so that it becomes more clear that it handles tags as well? I do get confused at times.
Review

The setting name IS confusing. But but it handles the tags/sources caching.

The setting name IS confusing. But but it handles the tags/sources caching.
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns tags
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
assertSame(null, testTags)
coVerify(exactly = 0) { api.tags() }
verify(exactly = 0) { db.tagsQueries.tags().executeAsList() }
AmineB marked this conversation as resolved Outdated

Why is this called twice ?

Why is this called twice ?

To test that api.tags() gets called only once regardless of how many calls are made to the repository.

To test that api.tags() gets called only once regardless of how many calls are made to the repository.
}
@Test
fun `Get tags without connection`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns tags
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
assertNotSame(tags, testTags)
assertSame(tagsDB.first().name, testTags?.first()?.tag)
coVerify(exactly = 0) { api.tags() }
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
}
@Test
fun `Get tags without connection and items caching disabled`() {
val tags = listOf(SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0))
val tagsDB = listOf(TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0))
coEvery { api.tags() } returns tags
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { connectivityStatus.isNetworkConnected } returns MutableStateFlow(false)
every { appSettingsService.isItemCachingEnabled() } returns false
val repository = Repository(api, appSettingsService, connectivityStatus, db)
var testTags: List<SelfossModel.Tag>? = null
AmineB marked this conversation as resolved Outdated

Tags and sources caching is handled via the update_sources setting.

Also, disabling that setting should clear the DB (this is not the case yet)

So this line should return null, as we won't check for the update_sources setting every time we try to fetch the db data.

Tags and sources caching is handled via the update_sources setting. Also, disabling that setting should clear the DB (this is not the case yet) So this line should return null, as we won't check for the update_sources setting every time we try to fetch the db data.

Returning null isn't possible, so it should be an empty list.

Returning `null` isn't possible, so it should be an empty list.

Why is tags caching handled by update_sources?

Why is tags caching handled by update_sources?

Because it's handled separatly, so users can cache the sources/tags without caching anything else.

Because it's handled separatly, so users can cache the sources/tags without caching anything else.

I don't think sources and tags should be cached if items are not cached; what would be the use of such a function?

I don't think sources and tags should be cached if items are not cached; what would be the use of such a function?

I don't think sources and tags should be cached if items are not cached; what would be the use of such a function?

As mentionnend by you in a previous comment, it is used to limit the ammount of calls to the selfoss instance, by limiting the call to tags/sources routes.

> I don't think sources and tags should be cached if items are not cached; what would be the use of such a function? As mentionnend by you in a previous comment, it is used to limit the ammount of calls to the selfoss instance, by limiting the call to tags/sources routes.
runBlocking {
testTags = repository.getTags()
}
assertSame(null, testTags)
coVerify(exactly = 0) { api.tags() }
verify(exactly = 0) { db.tagsQueries.tags().executeAsList() }
}
}
AmineB marked this conversation as resolved
Review

this should be assertEquals(emptyList(), testTags)

this should be `assertEquals(emptyList(), testTags)`
fun generateTestDBItems(item : FakeItemParameters = FakeItemParameters()) : List<ITEM> {