Skip to content

Commit 8c09f8e

Browse files
refactor data and domain code and tests
1 parent a9b7e9d commit 8c09f8e

12 files changed

Lines changed: 309 additions & 235 deletions

File tree

libraries/data/src/main/java/com/smarttoolfactory/data/model/IEntity.kt renamed to libraries/data/src/main/java/com/smarttoolfactory/data/model/Mappables.kt

File renamed without changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.smarttoolfactory.data.model.local
2+
3+
import androidx.room.Embedded
4+
import androidx.room.Entity
5+
import androidx.room.ForeignKey
6+
import androidx.room.Index
7+
import androidx.room.PrimaryKey
8+
import androidx.room.Relation
9+
10+
@Entity(tableName = "post")
11+
data class PostEntity(
12+
@PrimaryKey
13+
val id: Int,
14+
val userId: Int,
15+
val title: String,
16+
val body: String
17+
)
18+
19+
/**
20+
* * Data class that contains [PostStatus] data.
21+
* [PostEntity.id] is in [PostEntity] class, [PostStatus.postId] is in [PostStatus]
22+
* both points to same value.
23+
*
24+
* * [PostStatus.id] is auto generated by insertion to table.
25+
*
26+
* * Index let's this table to be sorted by postId which makes all
27+
* rows with same postId to be found faster.
28+
*
29+
* * Status of the [PostEntity] with [PostEntity.id] or [PostStatus.postId] belong to current user
30+
* logged in with [PostStatus.userAccountId] or -1 if any user hasn't logged in
31+
*/
32+
@Entity(
33+
tableName = "post_status",
34+
indices = [Index(value = ["userAccountId", "postId"])],
35+
foreignKeys = [
36+
ForeignKey(
37+
entity = PostEntity::class,
38+
parentColumns = ["id"],
39+
childColumns = ["postId"],
40+
onDelete = ForeignKey.NO_ACTION
41+
)
42+
]
43+
)
44+
data class PostStatus(
45+
@PrimaryKey(autoGenerate = true)
46+
val id: Int = 0,
47+
val userAccountId: Int = -1,
48+
val postId: Int,
49+
val displayCount: Int = 0,
50+
val isFavorite: Boolean = false
51+
)
52+
53+
/**
54+
* @Embedded tag is for having nested entities that are contained inside another entity. For
55+
* instance Songs are embedded inside an Album.
56+
*
57+
* @Relation is for having relation between entities based on pairing one or more properties,
58+
* such as ids. For instance Person with id, having Pets that has userId that is exactly same
59+
* with each other.
60+
*
61+
* * ParentColumn name from [PostEntity] class is matched with entityColumn
62+
* from [PostStatus.postId]
63+
*/
64+
data class PostAndStatus(
65+
66+
@Embedded
67+
val postEntity: PostEntity,
68+
69+
// 🔥 'id' comes from Post, 'postId' comes from Post. Both are the same ids
70+
@Relation(parentColumn = "id", entityColumn = "postId")
71+
var postStatus: PostStatus? = null
72+
)

libraries/data/src/main/java/com/smarttoolfactory/data/repository/RepositoryImpl.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ class PropertyRepositoryImplCoroutines @Inject constructor(
2020
) : PropertyRepositoryCoroutines {
2121

2222
override suspend fun fetchEntitiesFromRemote(orderBy: String): List<PropertyEntity> {
23+
val data = remoteDataSource.getPropertyDTOs(orderBy)
2324
saveSortOrderKey(orderBy)
24-
return mapper.map(remoteDataSource.getPropertyDTOs(orderBy))
25+
return mapper.map(data)
2526
}
2627

2728
override suspend fun getPropertyEntitiesFromLocal(): List<PropertyEntity> {
@@ -97,13 +98,12 @@ class PagedPropertyRepositoryImpl @Inject constructor(
9798
orderBy: String
9899
): List<PagedPropertyEntity> {
99100

100-
saveSortOrderKey(orderBy)
101-
return mapper.map(
102-
remoteDataSource.getPropertyDTOsWithPagination(
103-
currentPageNumber++,
104-
orderBy
105-
)
101+
val data = remoteDataSource.getPropertyDTOsWithPagination(
102+
currentPageNumber++,
103+
orderBy
106104
)
105+
saveSortOrderKey(orderBy)
106+
return mapper.map(data)
107107
}
108108

109109
override suspend fun getPropertyCount(): Int {
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.smarttoolfactory.data.repository
2+
3+
import com.google.common.truth.Truth
4+
import com.smarttoolfactory.data.constant.ORDER_BY_NONE
5+
import com.smarttoolfactory.data.mapper.MapperFactory
6+
import com.smarttoolfactory.data.mapper.PropertyDTOtoPagedEntityListMapper
7+
import com.smarttoolfactory.data.model.local.PagedPropertyEntity
8+
import com.smarttoolfactory.data.model.remote.PropertyResponse
9+
import com.smarttoolfactory.data.source.LocalPagedPropertyDataSource
10+
import com.smarttoolfactory.data.source.RemotePropertyDataSourceCoroutines
11+
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH
12+
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1
13+
import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2
14+
import com.smarttoolfactory.test_utils.util.convertToObjectFromJson
15+
import com.smarttoolfactory.test_utils.util.getResourceAsText
16+
import io.mockk.clearMocks
17+
import io.mockk.coEvery
18+
import io.mockk.coVerify
19+
import io.mockk.coVerifyOrder
20+
import io.mockk.every
21+
import io.mockk.just
22+
import io.mockk.mockk
23+
import io.mockk.runs
24+
import io.mockk.slot
25+
import kotlinx.coroutines.test.runBlockingTest
26+
import org.junit.jupiter.api.AfterEach
27+
import org.junit.jupiter.api.BeforeEach
28+
import org.junit.jupiter.api.Test
29+
30+
internal class PagedPropertyRepositoryImplTest {
31+
32+
private lateinit var repository: PagedPropertyRepository
33+
34+
private val localDataSource: LocalPagedPropertyDataSource = mockk()
35+
private val remoteDataSource: RemotePropertyDataSourceCoroutines = mockk()
36+
private val mapper: PropertyDTOtoPagedEntityListMapper = mockk()
37+
38+
companion object {
39+
40+
private val propertyResponse = convertToObjectFromJson<PropertyResponse>(
41+
getResourceAsText(RESPONSE_JSON_PATH)
42+
)!!
43+
44+
private val propertyDTOList = propertyResponse.res
45+
46+
private val entityList =
47+
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
48+
.map(propertyDTOList)
49+
50+
// FIXME Cannot convert from Json to Entity even with wrapper, check out Moshi or Jackson
51+
52+
private val propertyResponsePage1 = convertToObjectFromJson<PropertyResponse>(
53+
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
54+
)!!
55+
56+
private val propertyResponsePage2 = convertToObjectFromJson<PropertyResponse>(
57+
getResourceAsText(RESPONSE_JSON_PATH_PAGE_2)
58+
)!!
59+
60+
private val propertyDTOListPage1 = propertyResponsePage1.res
61+
private val propertyDTOListPage2 = propertyResponsePage2.res
62+
63+
private val entityListPage1 =
64+
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
65+
.map(
66+
convertToObjectFromJson<PropertyResponse>(
67+
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
68+
)!!.res
69+
)
70+
71+
private val entityListPage2 =
72+
MapperFactory.createListMapper<PropertyDTOtoPagedEntityListMapper>()
73+
.map(
74+
convertToObjectFromJson<PropertyResponse>(
75+
getResourceAsText(RESPONSE_JSON_PATH_PAGE_1)
76+
)!!.res
77+
)
78+
}
79+
80+
@Test
81+
fun `given page 2 returned data returned should have current page number 2 with Pagination`() =
82+
runBlockingTest {
83+
84+
// GIVEN
85+
val slot = slot<String>()
86+
87+
// Page 1 Pagination
88+
val page1DTO = propertyDTOListPage1
89+
val page1Data = entityListPage1
90+
91+
coEvery {
92+
remoteDataSource.getPropertyDTOsWithPagination(1)
93+
} returns page1DTO
94+
95+
every { mapper.map(page1DTO) } returns page1Data
96+
coEvery { localDataSource.saveOrderKey(capture(slot)) } just runs
97+
98+
// Page 2 Pagination
99+
val page2DTO = propertyDTOListPage2
100+
val page2Data = entityListPage2
101+
102+
coEvery {
103+
remoteDataSource.getPropertyDTOsWithPagination(2)
104+
} returns page2DTO
105+
106+
every { mapper.map(page2DTO) } returns page2Data
107+
108+
// WHEN
109+
val page1 = repository.getCurrentPageNumber()
110+
val expected1 = repository.fetchEntitiesFromRemoteByPage()
111+
112+
val page2 = repository.getCurrentPageNumber()
113+
val expected2 = repository.fetchEntitiesFromRemoteByPage()
114+
115+
// THEN
116+
Truth.assertThat(expected1).isEqualTo(page1Data)
117+
Truth.assertThat(page1).isEqualTo(1)
118+
119+
Truth.assertThat(expected2).isEqualTo(page2Data)
120+
Truth.assertThat(page2).isEqualTo(2)
121+
122+
coVerifyOrder {
123+
remoteDataSource.getPropertyDTOsWithPagination(1)
124+
localDataSource.saveOrderKey(ORDER_BY_NONE)
125+
mapper.map(page1DTO)
126+
remoteDataSource.getPropertyDTOsWithPagination(2)
127+
localDataSource.saveOrderKey(ORDER_BY_NONE)
128+
mapper.map(page2DTO)
129+
}
130+
}
131+
132+
@Test
133+
fun `given DB is empty should return an empty list`() = runBlockingTest {
134+
135+
// GIVEN
136+
val expected = listOf<PagedPropertyEntity>()
137+
coEvery { localDataSource.getPropertyEntities() } returns expected
138+
139+
// WHEN
140+
val actual = repository.getPropertyEntitiesFromLocal()
141+
142+
// THEN
143+
Truth.assertThat(actual).isEmpty()
144+
coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
145+
}
146+
147+
@Test
148+
fun `given DB is populated should return data list`() = runBlockingTest {
149+
150+
// GIVEN
151+
coEvery { localDataSource.getPropertyEntities() } returns entityList
152+
153+
// WHEN
154+
val actual = repository.getPropertyEntitiesFromLocal()
155+
156+
// THEN
157+
Truth.assertThat(actual)
158+
.containsExactlyElementsIn(entityList)
159+
coVerify(exactly = 1) { localDataSource.getPropertyEntities() }
160+
}
161+
162+
@Test
163+
fun `given entities, should save entities`() = runBlockingTest {
164+
165+
// GIVEN
166+
val idList = entityList.map {
167+
it.id.toLong()
168+
}
169+
170+
coEvery {
171+
localDataSource.saveEntities(entityList)
172+
} returns idList
173+
174+
// WHEN
175+
repository.savePropertyEntities(entityList)
176+
177+
// THEN
178+
coVerify(exactly = 1) { localDataSource.saveEntities(entityList) }
179+
}
180+
181+
@Test
182+
fun `given no error should delete entities`() = runBlockingTest {
183+
184+
// GIVEN
185+
coEvery { localDataSource.deletePropertyEntities() } just runs
186+
187+
// WHEN
188+
repository.deletePropertyEntities()
189+
190+
// THEN
191+
coVerify(exactly = 1) {
192+
localDataSource.deletePropertyEntities()
193+
}
194+
}
195+
196+
@BeforeEach
197+
fun setUp() {
198+
repository = PagedPropertyRepositoryImpl(localDataSource, remoteDataSource, mapper)
199+
}
200+
201+
@AfterEach
202+
fun tearDown() {
203+
clearMocks(localDataSource, remoteDataSource, mapper)
204+
}
205+
}

0 commit comments

Comments
 (0)