diff --git a/README.md b/README.md new file mode 100644 index 0000000..0787711 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# 2μ£Όμ°¨ λ―Έμ…˜ + +## 🎲 1단계 +### GithubProfileRepository ++ λ‘œμ»¬μ— μœ μ € 정보가 μžˆμŒμ„ 확인할 수 μžˆλ‹€ ++ λ‘œμ»¬μ— μœ μ € 정보가 μ—†μŒμ„ 확인할 수 μžˆλ‹€ ++ λ‘œμ»¬μ— μœ μ € 정보가 이미 μ‘΄μž¬ν•  λ•Œ μœ μ € 정보λ₯Ό λ°˜ν™˜ν•  수 μžˆλ‹€ ++ μœ μ € 정보가 없을 λ•Œ μ„œλ²„λ‘œλΆ€ν„° μœ μ € 정보λ₯Ό λ°›μ•„μ˜€λŠ” 것을 확인할 수 μžˆλ‹€ \ No newline at end of file diff --git a/app/src/main/java/com/malibin/study/github/presentation/sample/TestViewModel.kt b/app/src/main/java/com/malibin/study/github/presentation/sample/TestViewModel.kt new file mode 100644 index 0000000..a81449b --- /dev/null +++ b/app/src/main/java/com/malibin/study/github/presentation/sample/TestViewModel.kt @@ -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 +} + +class TestViewModel( + private val countMemory: CountMemory, +) : ViewModel() { + + private val _count = MutableLiveData(0) + val count: LiveData = _count + + private val _histories = MutableLiveData>() + val histories: LiveData> = _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 +} + +class TestViewModelWithCoroutine( + private val countMemory: CountMemoryWithCoroutine, +) : ViewModel() { + + private val _count = MutableLiveData(0) + val count: LiveData = _count + + private val _histories = MutableLiveData>() + val histories: LiveData> = _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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/malibin/study/github/presentation/sample/TodoManager.kt b/app/src/main/java/com/malibin/study/github/presentation/sample/TodoManager.kt index 835a922..799ef19 100644 --- a/app/src/main/java/com/malibin/study/github/presentation/sample/TodoManager.kt +++ b/app/src/main/java/com/malibin/study/github/presentation/sample/TodoManager.kt @@ -7,20 +7,20 @@ class TodoManager( ) { private val currentTodos = mutableListOf() - fun getTodos(): List { + suspend fun getTodos(): List { return currentTodos.toList() } - fun getTodoHistories(): List { + suspend fun getTodoHistories(): List { return todoMemory.getHistory() } - fun createTodo(todo: String) { + suspend fun createTodo(todo: String) { currentTodos.add(todo) todoMemory.create(todo) } - fun finishTodo(todo: String) { + suspend fun finishTodo(todo: String) { currentTodos.remove(todo) todoMemory.finish(todo) } diff --git a/app/src/test/java/com/malibin/study/github/data/repository/DefaultGithubProfileRepositoryTest.kt b/app/src/test/java/com/malibin/study/github/data/repository/DefaultGithubProfileRepositoryTest.kt new file mode 100644 index 0000000..2ec2c92 --- /dev/null +++ b/app/src/test/java/com/malibin/study/github/data/repository/DefaultGithubProfileRepositoryTest.kt @@ -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(relaxed = true) + fakeRemoteSource = mockk(relaxed = true) + } + + @Test + fun `λ‘œμ»¬μ— μœ μ € 정보가 μžˆμŒμ„ 확인할 수 μžˆλ‹€`() = runBlocking { + // given + val expectedName = "stopkite" + + coEvery { fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName } returns "stopkite" + + // when + val actualSearch = fakeLocalSource.getGithubProfile("stopkite").getOrThrow().userName + + // then + assertThat(actualSearch).isEqualTo(expectedName) + } + + @Test + fun `λ‘œμ»¬μ— μœ μ € 정보가 μ—†μŒμ„ 확인할 수 μžˆλ‹€`() = runBlocking { + // 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 } + + // when + val actualLocalProfile = fakeLocalSource.getGithubProfile("stopkite").getOrThrow() + + // then + assertThat(actualLocalProfile).isEqualTo(githubProfile) + } + + @Test + fun `μœ μ € 정보가 없을 λ•Œ μ„œλ²„λ‘œλΆ€ν„° μœ μ € 정보λ₯Ό λ°›μ•„μ˜€λŠ” 것을 확인할 수 μžˆλ‹€`() = runBlocking { + // given + 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) + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/malibin/study/github/presentation/sample/TestViewModelTest.kt b/app/src/test/java/com/malibin/study/github/presentation/sample/TestViewModelTest.kt new file mode 100644 index 0000000..d900c72 --- /dev/null +++ b/app/src/test/java/com/malibin/study/github/presentation/sample/TestViewModelTest.kt @@ -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(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(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 κ°€ ν•œ 번 ν˜ΈμΆœλ˜μ—ˆλ‹€ + ) + + } + + +} \ No newline at end of file diff --git a/app/src/test/java/com/malibin/study/github/presentation/sample/TodoManagerTest.kt b/app/src/test/java/com/malibin/study/github/presentation/sample/TodoManagerTest.kt new file mode 100644 index 0000000..c6ebebe --- /dev/null +++ b/app/src/test/java/com/malibin/study/github/presentation/sample/TodoManagerTest.kt @@ -0,0 +1,114 @@ +package com.malibin.study.github.presentation.sample + +import com.google.common.truth.Truth.assertThat +import io.mockk.* +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +/** + * μ•ˆμ— μžˆλŠ” κ΅¬ν˜„λ“€μ„ μ „λΆ€ λ‹€ μ•„λŠ” ν…ŒμŠ€νŠΈ -> ν™”μ΄νŠΈ λ°•μŠ€ ν…ŒμŠ€νŠΈ + */ +internal class TodoManagerTest { + + /*@Test + fun ddd() { + // given + // κ°€μ§œ 객체λ₯Ό λ§Œλ“ λ‹€ -> mock + // κ·Έλž˜μ„œ 라이브러리 이름도 mockk : 순수 μ½”ν‹€λ¦°μœΌλ‘œ 이루어짐 + val todoMemory = mockk() + + // λͺ¨λ“  todoMemoryλŠ” ~을 λ°˜ν™˜ν•œλ‹€ + every { todoMemory.getHistory() } returns listOf("todo1", "todo2") + + + // μ–΄λ–€ κΈ°λŠ₯을 ν–ˆλŠ”μ§€ λ‚΄λΆ€μ μœΌλ‘œ κ΅¬ν˜„λ„ ν•  수 μžˆλ‹€ + // + + val fakeTodoMemory = object : TodoMemory { + + private val list = mutableListOf() + + override fun getHistory(): List { + return list + } + + override fun create(todo: String) { + this.list.add(todo) + } + + override fun finish(todo: String) { + + } + } + + //val todoManager = TodoManager(fakeTodoMemory) + //todoManager.createTodo("todo1") + //todoManager.createTodo("todo2") + + val todoManager = TodoManager(todoMemory) + + // when + val actualHistories = todoManager.getTodoHistories() + + // then + assertThat(actualHistories.size).isEqualTo(2) + assertThat(actualHistories) // λ˜‘κ°™μ€ 값듀이 λ“€μ–΄ μžˆμŒμ„ 확인 + .containsExactlyElementsIn(listOf("todo1", "todo2")) + .inOrder() + + }*/ + + /** createTodo ν•  λ•Œ μ–΄λ–€ λ™μž‘μ„ ν•΄μ•Όν•˜λŠ”μ§€ mockkμ—κ²Œ μ•Œλ €μ£Όμ–΄μ•Ό ν•œλ‹€ + * ν•˜μ§€λ§Œ relaxed = true ν‚€μ›Œλ“œλ₯Ό μ“°λ©΄ μ•ˆν•΄λ„ 됨! + * */ + @Test + fun ddd3() { + // given + /*val todoMemory = mockk(relaxed = true) + + val todoManager = TodoManager(todoMemory) + +// every { todoMemory.create("2") } throws java.lang.IllegalArgumentException("") +// every { todoMemory.create("1") } just Runs + + todoManager.createTodo("1") + todoManager.createTodo("2") // μ—¬κΈ°μ„œ throw + + // when + val actualTodos = todoManager.getTodos() + + // then + assertAll( + { assertThat(actualTodos).isEqualTo(listOf("1", "2")) }, + { verify(exactly = 1) { todoMemory.create("1") } }, // 1을 넣은 것이 ν•œ 번 λ°œμƒν–ˆλ‹€ + { verify(exactly = 1) { todoMemory.create(any()) } } // 무적 any !! + )*/ + } + + + /** 코루틴을 μ“΄λ‹€κ³  κ°€μ •ν•΄λ³΄μž + * suspend λŠ” 코루틴 scope λ‚΄μ—μ„œ 싀행을 ν•΄μ€˜μ•Όν•œλ‹€ + * everyλŠ” 코루틴 scopeκ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— λͺ»μ“΄λ‹€ γ… γ…  + * 그러기 λ•Œλ¬Έμ— coλ₯Ό λΆ™μ΄μž! coEvery + * */ + @Test + fun ddd5() = runBlocking { + // given + val todoMemory = mockk() + + coEvery { todoMemory.getHistory() } returns listOf("todo1", "todo2") + + val todoManager = TodoManager(todoMemory) + + // when + val actualHistories = todoManager.getTodoHistories() + + // then + assertThat(actualHistories.size).isEqualTo(2) + assertThat(actualHistories) // λ˜‘κ°™μ€ 값듀이 λ“€μ–΄ μžˆμŒμ„ 확인 + .containsExactlyElementsIn(listOf("todo1", "todo2")) + .inOrder() + + } +} \ No newline at end of file