Skip to content

Commit

Permalink
refactor data and domain code and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SmartToolFactory committed Sep 9, 2020
1 parent a9b7e9d commit 8c09f8e
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 235 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.smarttoolfactory.data.model.local

import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.Relation

@Entity(tableName = "post")
data class PostEntity(
@PrimaryKey
val id: Int,
val userId: Int,
val title: String,
val body: String
)

/**
* * Data class that contains [PostStatus] data.
* [PostEntity.id] is in [PostEntity] class, [PostStatus.postId] is in [PostStatus]
* both points to same value.
*
* * [PostStatus.id] is auto generated by insertion to table.
*
* * Index let's this table to be sorted by postId which makes all
* rows with same postId to be found faster.
*
* * Status of the [PostEntity] with [PostEntity.id] or [PostStatus.postId] belong to current user
* logged in with [PostStatus.userAccountId] or -1 if any user hasn't logged in
*/
@Entity(
tableName = "post_status",
indices = [Index(value = ["userAccountId", "postId"])],
foreignKeys = [
ForeignKey(
entity = PostEntity::class,
parentColumns = ["id"],
childColumns = ["postId"],
onDelete = ForeignKey.NO_ACTION
)
]
)
data class PostStatus(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val userAccountId: Int = -1,
val postId: Int,
val displayCount: Int = 0,
val isFavorite: Boolean = false
)

/**
* @Embedded tag is for having nested entities that are contained inside another entity. For
* instance Songs are embedded inside an Album.
*
* @Relation is for having relation between entities based on pairing one or more properties,
* such as ids. For instance Person with id, having Pets that has userId that is exactly same
* with each other.
*
* * ParentColumn name from [PostEntity] class is matched with entityColumn
* from [PostStatus.postId]
*/
data class PostAndStatus(

@Embedded
val postEntity: PostEntity,

// 🔥 'id' comes from Post, 'postId' comes from Post. Both are the same ids
@Relation(parentColumn = "id", entityColumn = "postId")
var postStatus: PostStatus? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ class PropertyRepositoryImplCoroutines @Inject constructor(
) : PropertyRepositoryCoroutines {

override suspend fun fetchEntitiesFromRemote(orderBy: String): List<PropertyEntity> {
val data = remoteDataSource.getPropertyDTOs(orderBy)
saveSortOrderKey(orderBy)
return mapper.map(remoteDataSource.getPropertyDTOs(orderBy))
return mapper.map(data)
}

override suspend fun getPropertyEntitiesFromLocal(): List<PropertyEntity> {
Expand Down Expand Up @@ -97,13 +98,12 @@ class PagedPropertyRepositoryImpl @Inject constructor(
orderBy: String
): List<PagedPropertyEntity> {

saveSortOrderKey(orderBy)
return mapper.map(
remoteDataSource.getPropertyDTOsWithPagination(
currentPageNumber++,
orderBy
)
val data = remoteDataSource.getPropertyDTOsWithPagination(
currentPageNumber++,
orderBy
)
saveSortOrderKey(orderBy)
return mapper.map(data)
}

override suspend fun getPropertyCount(): Int {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package com.smarttoolfactory.data.repository

import com.google.common.truth.Truth
import com.smarttoolfactory.data.constant.ORDER_BY_NONE
import com.smarttoolfactory.data.mapper.MapperFactory
import com.smarttoolfactory.data.mapper.PropertyDTOtoPagedEntityListMapper
import com.smarttoolfactory.data.model.local.PagedPropertyEntity
import com.smarttoolfactory.data.model.remote.PropertyResponse
import com.smarttoolfactory.data.source.LocalPagedPropertyDataSource
import com.smarttoolfactory.data.source.RemotePropertyDataSourceCoroutines
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2
import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
import com.smarttoolfactory.test_utils.util.getResourceAsText
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.slot
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

internal class PagedPropertyRepositoryImplTest {

private lateinit var repository: PagedPropertyRepository

private val localDataSource: LocalPagedPropertyDataSource = mockk()
private val remoteDataSource: RemotePropertyDataSourceCoroutines = mockk()
private val mapper: PropertyDTOtoPagedEntityListMapper = mockk()

companion object {

private val propertyResponse = convertToObjectFromJson<PropertyResponse>(
getResourceAsText(RESPONSE_JSON_PATH)
)!!

private val propertyDTOList = propertyResponse.res

private val entityList =
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
.map(propertyDTOList)

// FIXME Cannot convert from Json to Entity even with wrapper, check out Moshi or Jackson

private val propertyResponsePage1 = convertToObjectFromJson<PropertyResponse>(
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
)!!

private val propertyResponsePage2 = convertToObjectFromJson<PropertyResponse>(
getResourceAsText(RESPONSE_JSON_PATH_PAGE_2)
)!!

private val propertyDTOListPage1 = propertyResponsePage1.res
private val propertyDTOListPage2 = propertyResponsePage2.res

private val entityListPage1 =
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
.map(
convertToObjectFromJson<PropertyResponse>(
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
)!!.res
)

private val entityListPage2 =
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
.map(
convertToObjectFromJson<PropertyResponse>(
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
)!!.res
)
}

@Test
fun `given page 2 returned data returned should have current page number 2 with Pagination`() =
runBlockingTest {

// GIVEN
val slot = slot<String>()

// Page 1 Pagination
val page1DTO = propertyDTOListPage1
val page1Data = entityListPage1

coEvery {
remoteDataSource.getPropertyDTOsWithPagination(1)
} returns page1DTO

every { mapper.map(page1DTO) } returns page1Data
coEvery { localDataSource.saveOrderKey(capture(slot)) } just runs

// Page 2 Pagination
val page2DTO = propertyDTOListPage2
val page2Data = entityListPage2

coEvery {
remoteDataSource.getPropertyDTOsWithPagination(2)
} returns page2DTO

every { mapper.map(page2DTO) } returns page2Data

// WHEN
val page1 = repository.getCurrentPageNumber()
val expected1 = repository.fetchEntitiesFromRemoteByPage()

val page2 = repository.getCurrentPageNumber()
val expected2 = repository.fetchEntitiesFromRemoteByPage()

// THEN
Truth.assertThat(expected1).isEqualTo(page1Data)
Truth.assertThat(page1).isEqualTo(1)

Truth.assertThat(expected2).isEqualTo(page2Data)
Truth.assertThat(page2).isEqualTo(2)

coVerifyOrder {
remoteDataSource.getPropertyDTOsWithPagination(1)
localDataSource.saveOrderKey(ORDER_BY_NONE)
mapper.map(page1DTO)
remoteDataSource.getPropertyDTOsWithPagination(2)
localDataSource.saveOrderKey(ORDER_BY_NONE)
mapper.map(page2DTO)
}
}

@Test
fun `given DB is empty should return an empty list`() = runBlockingTest {

// GIVEN
val expected = listOf<PagedPropertyEntity>()
coEvery { localDataSource.getPropertyEntities() } returns expected

// WHEN
val actual = repository.getPropertyEntitiesFromLocal()

// THEN
Truth.assertThat(actual).isEmpty()
coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
}

@Test
fun `given DB is populated should return data list`() = runBlockingTest {

// GIVEN
coEvery { localDataSource.getPropertyEntities() } returns entityList

// WHEN
val actual = repository.getPropertyEntitiesFromLocal()

// THEN
Truth.assertThat(actual)
.containsExactlyElementsIn(entityList)
coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
}

@Test
fun `given entities, should save entities`() = runBlockingTest {

// GIVEN
val idList = entityList.map {
it.id.toLong()
}

coEvery {
localDataSource.saveEntities(entityList)
} returns idList

// WHEN
repository.savePropertyEntities(entityList)

// THEN
coVerify(exactly = 1) { localDataSource.saveEntities(entityList) }
}

@Test
fun `given no error should delete entities`() = runBlockingTest {

// GIVEN
coEvery { localDataSource.deletePropertyEntities() } just runs

// WHEN
repository.deletePropertyEntities()

// THEN
coVerify(exactly = 1) {
localDataSource.deletePropertyEntities()
}
}

@BeforeEach
fun setUp() {
repository = PagedPropertyRepositoryImpl(localDataSource, remoteDataSource, mapper)
}

@AfterEach
fun tearDown() {
clearMocks(localDataSource, remoteDataSource, mapper)
}
}
Loading

0 comments on commit 8c09f8e

Please sign in to comment.