-
Notifications
You must be signed in to change notification settings - Fork 5
Step1 미션 제출합니다 #2
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
base: stopkite
Are you sure you want to change the base?
Changes from all commits
f73d910
d19d120
2c67049
374f8f5
7d56fd4
a87ea25
e26d4a0
198c0fb
b865223
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # 2주차 미션 | ||
|
|
||
| ## 🎲 1단계 | ||
| ### GithubProfileRepository | ||
| + 로컬에 유저 정보가 있음을 확인할 수 있다 | ||
| + 로컬에 유저 정보가 없음을 확인할 수 있다 | ||
| + 로컬에 유저 정보가 이미 존재할 때 유저 정보를 반환할 수 있다 | ||
| + 유저 정보가 없을 때 서버로부터 유저 정보를 받아오는 것을 확인할 수 있다 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.malibin.study.github.presentation.sample | ||
|
|
||
| import androidx.lifecycle.* | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| interface CountMemory { | ||
| fun save(history: String) | ||
| fun getHistories(): List<String> | ||
| } | ||
|
|
||
| class TestViewModel( | ||
| private val countMemory: CountMemory, | ||
| ) : ViewModel() { | ||
|
|
||
| private val _count = MutableLiveData<Int>(0) | ||
| val count: LiveData<Int> = _count | ||
|
|
||
| private val _histories = MutableLiveData<List<String>>() | ||
| val histories: LiveData<List<String>> = _histories | ||
|
|
||
| fun increase() { | ||
| _count.value = _count.value?.plus(1) | ||
| countMemory.save("increase") | ||
| } | ||
|
|
||
| fun decrease() { | ||
| _count.value = _count.value?.minus(1) | ||
| countMemory.save("decrease") | ||
| } | ||
|
|
||
| fun loadHistories() { | ||
| _histories.value = countMemory.getHistories() | ||
| } | ||
| } | ||
|
|
||
|
|
||
| //======================================================================================= | ||
|
|
||
|
|
||
| interface CountMemoryWithCoroutine { | ||
| suspend fun save(history: String) | ||
| suspend fun getHistories(): List<String> | ||
| } | ||
|
|
||
| class TestViewModelWithCoroutine( | ||
| private val countMemory: CountMemoryWithCoroutine, | ||
| ) : ViewModel() { | ||
|
|
||
| private val _count = MutableLiveData<Int>(0) | ||
| val count: LiveData<Int> = _count | ||
|
|
||
| private val _histories = MutableLiveData<List<String>>() | ||
| val histories: LiveData<List<String>> = _histories | ||
|
|
||
| fun increase() { | ||
| viewModelScope.launch { | ||
| _count.value = _count.value?.plus(1) | ||
| countMemory.save("increase") | ||
| } | ||
| } | ||
|
|
||
| fun decrease() { | ||
| viewModelScope.launch { | ||
| _count.value = _count.value?.minus(1) | ||
| countMemory.save("decrease") | ||
| } | ||
| } | ||
|
|
||
| fun loadHistories() { | ||
| viewModelScope.launch { | ||
| _histories.value = countMemory.getHistories() | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package com.malibin.study.github.data.repository | ||
|
|
||
| import com.google.common.truth.Truth.assertThat | ||
| import com.malibin.study.github.data.source.GithubProfileSource | ||
| import com.malibin.study.github.domain.profile.GithubProfile | ||
| import com.malibin.study.github.domain.repository.GithubProfileRepository | ||
| import com.malibin.study.github.utils.InstantTaskExecutorExtension | ||
| import io.mockk.Runs | ||
| import io.mockk.coEvery | ||
| import io.mockk.mockk | ||
| import kotlinx.coroutines.runBlocking | ||
| import org.junit.jupiter.api.BeforeEach | ||
| import org.junit.jupiter.api.Test | ||
| import org.junit.jupiter.api.extension.RegisterExtension | ||
|
|
||
| internal class DefaultGithubProfileRepositoryTest { | ||
|
|
||
| private lateinit var fakeLocalSource: GithubProfileSource | ||
| private lateinit var fakeRemoteSource: GithubProfileSource | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| @RegisterExtension | ||
| val instantTaskExecutorExtension = InstantTaskExecutorExtension() | ||
| } | ||
|
|
||
| @BeforeEach | ||
| fun setUp() { | ||
| fakeLocalSource = mockk<GithubProfileSource>(relaxed = true) | ||
| fakeRemoteSource = mockk<GithubProfileSource>(relaxed = true) | ||
| } | ||
|
|
||
| @Test | ||
| fun `로컬에 유저 정보가 있음을 확인할 수 있다`() = runBlocking { | ||
| // given | ||
| val expectedName = "stopkite" | ||
|
|
||
| coEvery { fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName } returns "stopkite" | ||
|
|
||
|
Comment on lines
+35
to
+39
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .getGithubProfile()이 반환하는 것이 GithubProfile 객체이기에, 이 객체를 모두 하드코딩으로 직접 만들어주는 것이 바람직해요! 모킹은 작성해주신 것 처럼 체이닝을 길게 써도 가능은 하지만, 원하는 객체가 기대하는 값을 반환하게 적는 게 가장 좋아요 :) |
||
| // when | ||
| val actualSearch = fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName | ||
|
|
||
| // then | ||
| assertThat(actualSearch).isEqualTo(expectedName) | ||
| } | ||
|
|
||
| @Test | ||
| fun `로컬에 유저 정보가 없음을 확인할 수 있다`() = runBlocking { | ||
|
Comment on lines
+47
to
+48
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Repository에서는 로컬에 유저정보가 있는 지 없는 지에 대한 정보를 외부에 알리지 않고 있어요. getGithubProfile을 할 때 repository에서 무슨 일이 벌어지고 있는 지 테스트해보는 것은 어떨까요? |
||
| // given | ||
| val expectedName = "stopkite" | ||
|
|
||
| coEvery { fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName } returns "" | ||
|
|
||
| // when | ||
| val actualSearch = fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName | ||
|
|
||
| // then | ||
| assertThat(actualSearch).isNotEqualTo(expectedName) | ||
| } | ||
|
|
||
| @Test | ||
| fun `로컬에 유저 정보가 이미 존재할 때 유저 정보를 반환할 수 있다`() = runBlocking { | ||
| // given | ||
| val githubProfile = GithubProfile( | ||
| 0, | ||
| "stopkite", | ||
| "https://avatars.githubusercontent.com/u/62979643?v=4", | ||
| "Ji-Yeon", | ||
| "1", | ||
| 10, | ||
| 100 | ||
| ) | ||
|
|
||
| coEvery { fakeLocalSource.getGithubProfile("stopkite") } returns runCatching { githubProfile } | ||
|
Comment on lines
+61
to
+74
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remote source의 메서드는 정말로 호출하지 않았는 지 테스트해보는 것도 좋겠네요 :) |
||
|
|
||
| // when | ||
| val actualLocalProfile = fakeLocalSource.getGithubProfile("stopkite").getOrThrow() | ||
|
Comment on lines
+76
to
+77
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getOrThrow를 하면 정말로 throw가 발생할 수도 있기 때문에, (test fail) |
||
|
|
||
| // then | ||
| assertThat(actualLocalProfile).isEqualTo(githubProfile) | ||
| } | ||
|
|
||
| @Test | ||
| fun `유저 정보가 없을 때 서버로부터 유저 정보를 받아오는 것을 확인할 수 있다`() = runBlocking { | ||
| // given | ||
|
Comment on lines
+83
to
+85
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getGithubProfile을 할 때 유저 정보가 없음을 Repository는 무엇을 통해 알았는지 한 번 생각해보면 좋겠어요 :) |
||
| val expectedGithubProfile = GithubProfile( | ||
| 0, | ||
| "stopkite", | ||
| "https://avatars.githubusercontent.com/u/62979643?v=4", | ||
| "Ji-Yeon", | ||
| "1", | ||
| 10, | ||
| 100 | ||
| ) | ||
|
|
||
| coEvery { | ||
| fakeRemoteSource.getGithubProfile("stopkite") | ||
| } returns runCatching { expectedGithubProfile } | ||
|
|
||
| // when | ||
| val actualSavedLocalProfile = fakeRemoteSource.getGithubProfile("stopkite").getOrThrow() | ||
|
|
||
| // then | ||
| assertThat(actualSavedLocalProfile).isEqualTo(expectedGithubProfile) | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.malibin.study.github.presentation.sample | ||
|
|
||
| import com.google.common.truth.Truth.assertThat | ||
| import com.malibin.study.github.utils.InstantTaskExecutorExtension | ||
| import com.malibin.study.github.utils.MainCoroutineExtension | ||
| import com.malibin.study.github.utils.getOrAwaitValue | ||
| import io.mockk.coVerify | ||
| import io.mockk.mockk | ||
| import io.mockk.verify | ||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| import kotlinx.coroutines.runBlocking | ||
| import org.junit.jupiter.api.BeforeEach | ||
| import org.junit.jupiter.api.Test | ||
| import org.junit.jupiter.api.assertAll | ||
| import org.junit.jupiter.api.extension.RegisterExtension | ||
|
|
||
| internal class TestViewModelTest { | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| @RegisterExtension | ||
| val instantTaskExecutorExtension = InstantTaskExecutorExtension() | ||
| } | ||
|
|
||
| private lateinit var countMemory: CountMemory | ||
| private lateinit var testViewModel: TestViewModel | ||
|
|
||
| @BeforeEach | ||
| fun setUp() { | ||
| countMemory = mockk<CountMemory>(relaxed = true) | ||
| testViewModel = TestViewModel(countMemory) | ||
| } | ||
|
|
||
| @Test | ||
| fun test1() { | ||
| // when | ||
| testViewModel.increase() | ||
|
|
||
| // then | ||
| assertAll( | ||
| { assertThat(testViewModel.count.value).isEqualTo(1) }, | ||
| { verify(exactly = 1) { countMemory.save("increase") } } // increase 가 한 번 호출되었다 | ||
| ) | ||
|
|
||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| internal class TestViewModelWithCoroutineTest { | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| @RegisterExtension | ||
| val instantTaskExecutorExtension = InstantTaskExecutorExtension() | ||
|
|
||
| @JvmStatic | ||
| @RegisterExtension | ||
| val mce = MainCoroutineExtension() | ||
| } | ||
|
|
||
| private lateinit var countMemory: CountMemoryWithCoroutine | ||
| private lateinit var testViewModel: TestViewModelWithCoroutine | ||
|
|
||
| @BeforeEach | ||
| fun setUp() { | ||
| countMemory = mockk<CountMemoryWithCoroutine>(relaxed = true) | ||
| testViewModel = TestViewModelWithCoroutine(countMemory) | ||
| } | ||
|
|
||
| @Test | ||
| fun test1() = runBlocking { | ||
| // when | ||
| testViewModel.increase() | ||
|
|
||
| // then | ||
| assertAll( | ||
| { assertThat(testViewModel.count.getOrAwaitValue()).isEqualTo(1) }, | ||
| { coVerify(exactly = 1) { countMemory.save("increase") } } // increase 가 한 번 호출되었다 | ||
| ) | ||
|
|
||
| } | ||
|
|
||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 extension은 안드로이드 요소 (LiveData) 등이 끼어있는 유닛테스트를 할 때 추가해주시면 돼요!