Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ android {
dataBinding = true
viewBinding = true
}

testOptions {
unitTests.isReturnDefaultValues = true
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a: 어떤 옵션인가요?

Copy link
Copy Markdown
Contributor Author

@devfeijoa devfeijoa Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

당시에 로컬 unit test에서 Android 플랫폼 API를 건드리는 코드가 있어서, 예외로 테스트가 터지니까 일단 기본값으로 바꿔서 테스트를 돌리려고 임의로 넣어뒀던 설정입니다.
또한 옵션은 null 혹은 0으로 흘러가면서 버그가 가려질 수 있어서 해당 옵션 삭제하였습니다!
9804de8

https://developer.android.com/training/testing/local-tests#error

}

dependencies {
Expand Down Expand Up @@ -106,6 +110,8 @@ dependencies {

// 테스트 관련 라이브러리
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.coroutines.test)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.espresso.core)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

package com.into.websoso.domain.usecase

import com.into.websoso.data.model.ExploreResultEntity
import com.into.websoso.data.repository.NovelRepository
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test

class GetSearchedNovelsUseCaseTest {

private lateinit var getSearchedNovelsUseCase: GetSearchedNovelsUseCase
private val novelRepository: NovelRepository = mockk(relaxed = true)

private val dummyExploreResultEntity = ExploreResultEntity(
resultCount = 0L,
isLoadable = true,
novels = emptyList()
)

@Before
fun setUp() {
getSearchedNovelsUseCase = GetSearchedNovelsUseCase(novelRepository)
coEvery { novelRepository.fetchNormalExploreResult(any(), any(), any()) } returns dummyExploreResultEntity
}

@Test
fun `처음 검색하면 페이지는 0, 사이즈는 20으로 레포지토리에 요청한다`() = runTest {

// when
getSearchedNovelsUseCase("웹소설")

// then

coVerify(exactly = 1) {
novelRepository.fetchNormalExploreResult(
searchWord = "웹소설",
page = 0,
size = 20
)
}
}

@Test
fun `같은 검색어로 다시 검색하면 페이지는 1, 사이즈는 10으로 레포지토리에 요청한다`() = runTest {
// when
getSearchedNovelsUseCase("웹소설") // 0페이지 20개
getSearchedNovelsUseCase("웹소설") // 1페이지 10개

// then
coVerify(exactly = 1) {
novelRepository.fetchNormalExploreResult(
searchWord = "웹소설",
page = 1,
size = 10
)
}
}

@Test
fun `다른 검색어로 검색하면 캐시를 지우고 페이지는 0으로 레포지토리에 요청한다`() = runTest {
// when
getSearchedNovelsUseCase("웹소설") // clear 1회
clearMocks(novelRepository, answers = false)
getSearchedNovelsUseCase("새로운 웹소설")


// then
coVerify(exactly = 1) {
novelRepository.clearCachedNormalExploreResult()
novelRepository.fetchNormalExploreResult(
searchWord = "새로운 웹소설",
page = 0,
size = 20
)
}
}
}
1 change: 1 addition & 0 deletions domain/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ android {
dependencies {
implementation(projects.core.common)
implementation(projects.data.library)
testImplementation(libs.junit)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c: 앞으로 테스트를 어느범위까지 적용시킬 생각이신가요?
범위에 따라 테스트 의존성을 플러그인에 추가해도 좋을 것 같아서요

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 도메인 레이어 모델과 하나의 UseCase에 대해서만 로컬 유닛 테스트를 작성한 상태입니다.
추후 테스트 범위가 확장될 경우에는 의존성 관리 방식도 함께 정리하여, 모듈별 개별 추가보다는 공통 플러그인으로 관리하는 방향을 고려하겠습니다..!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.into.websoso.domain.library.model

import com.into.websoso.domain.library.model.AttractivePoints.Companion.toAttractivePoints
import org.junit.Assert.*
import org.junit.Test

class AttractivePointsTest {

@Test
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c: 이 아래로 gwt주석이 없는데, 유즈케이스 테스트랑 코드 스타일이 통일되면 좋을 것 같아요 주석이 다 있거나 아예 다 없거나!
개인적으론 유즈케이스에서 gwt로 문단나눠주신게 더 잘 읽혔습니다!

Copy link
Copy Markdown
Contributor Author

@devfeijoa devfeijoa Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 없는 테스트 케이스 모두 gwt으로 통일하였습니다!
1c05a89

fun `기본 상태에서는 아무 항목도 선택되지 않는다`() {
val points = AttractivePoints()

assertFalse(points.isSelected)
assertTrue(points.selectedAttractivePoints.isEmpty())
assertTrue(points.selectedLabels.isEmpty())
assertTrue(points.selectedKeys.isEmpty())

for (point in AttractivePoint.entries) {
assertFalse(points[point])
}
}

@Test
fun `항목을 선택하면 해당 항목만 선택 상태가 된다`() {
val points = AttractivePoints()
.set(AttractivePoint.WORLDVIEW)

assertTrue(points[AttractivePoint.WORLDVIEW])
assertTrue(points.isSelected)
assertEquals(listOf("세계관"), points.selectedLabels)
assertEquals(listOf("worldview"), points.selectedKeys)
}

@Test
fun `같은 항목을 다시 선택하면 선택이 해제된다`() {
val points = AttractivePoints()
.set(AttractivePoint.WORLDVIEW)
.set(AttractivePoint.WORLDVIEW)

assertFalse(points[AttractivePoint.WORLDVIEW])
assertFalse(points.isSelected)
}

@Test
fun `문자열 키 목록을 통해 선택 상태를 생성할 수 있다`() {
val keys = listOf("worldview", "character", "unknown")

val points = keys.toAttractivePoints()

assertTrue(points[AttractivePoint.WORLDVIEW])
assertTrue(points[AttractivePoint.CHARACTER])
assertFalse(points[AttractivePoint.MATERIAL])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.into.websoso.domain.library.model

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class NovelRatingTest {

@Test
fun `기본 평점은 선택되지 않은 상태이다`() {
val rating = NovelRating()

assertEquals(Rating.DEFAULT, rating.rating)
assertFalse(rating.isSelected)
}

@Test
fun `평점을 설정하면 선택 상태가 된다`() {
val rating = NovelRating.from(4.0f)

assertTrue(rating.isSelected)
assertEquals(Rating.FOUR, rating.rating)
}

@Test
fun `같은 평점을 다시 설정하면 기본 상태로 돌아간다`() {
val rating = NovelRating.from(3.0f)
.set(Rating.THREE)

assertEquals(Rating.DEFAULT, rating.rating)
assertFalse(rating.isSelected)
}

@Test
fun `평점은 근사값 비교로 판단된다`() {
val rating = NovelRating.from(4.0f)

assertTrue(rating.isCloseTo(Rating.from(4.00001f)))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.into.websoso.domain.library.model

import org.junit.Assert.assertEquals
import org.junit.Test

class RatingTest {

@Test
fun `정확한 값은 대응되는 평점으로 변환된다`() {
assertEquals(Rating.DEFAULT, Rating.from(0.0f))
assertEquals(Rating.ONE, Rating.from(1.0f))
assertEquals(Rating.THREE_POINT_FIVE, Rating.from(3.5f))
assertEquals(Rating.FOUR_POINT_EIGHT, Rating.from(4.8f))
assertEquals(Rating.FIVE, Rating.from(5.0f))
}

@Test
fun `근사값도 가까운 평점으로 변환된다`() {
assertEquals(Rating.FOUR, Rating.from(4.00001f))
assertEquals(Rating.FOUR_POINT_EIGHT, Rating.from(4.79999f))
}

@Test
fun `매칭되지 않는 값은 기본 평점으로 처리된다`() {
assertEquals(Rating.DEFAULT, Rating.from(-1.0f))
assertEquals(Rating.DEFAULT, Rating.from(4.7f))
assertEquals(Rating.DEFAULT, Rating.from(100.0f))
}
Comment on lines +36 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

// when 주석 들여쓰기 누락

Line 37의 // when 주석이 들여쓰기 없이 컬럼 0에 위치해 있어, 나머지 테스트 메서드들과 스타일이 불일치합니다.

🛠️ 수정 제안
     fun `매칭되지 않는 값은 기본 평점으로 처리된다`() {
-// when
+        // when
         val negative = Rating.from(-1.0f)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun `매칭되지 않는 값은 기본 평점으로 처리된다`() {
// when
val negative = Rating.from(-1.0f)
val unmatched = Rating.from(4.7f)
val overflow = Rating.from(100.0f)
// then
assertEquals(Rating.DEFAULT, negative)
assertEquals(Rating.DEFAULT, unmatched)
assertEquals(Rating.DEFAULT, overflow)
}
fun `매칭되지 않는 값은 기본 평점으로 처리된다`() {
// when
val negative = Rating.from(-1.0f)
val unmatched = Rating.from(4.7f)
val overflow = Rating.from(100.0f)
// then
assertEquals(Rating.DEFAULT, negative)
assertEquals(Rating.DEFAULT, unmatched)
assertEquals(Rating.DEFAULT, overflow)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@domain/library/src/test/java/com/into/websoso/domain/library/model/RatingTest.kt`
around lines 36 - 46, The test method `매칭되지 않는 값은 기본 평점으로 처리된다` has the `//
when` comment left-aligned at column 0; fix the formatting by indenting the `//
when` comment to match the other comments in this file (align with the
surrounding test comments in RatingTest.kt), keeping the comment directly above
the block that calls Rating.from and ensuring consistent indentation style for
this test method.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.into.websoso.domain.library.model

import com.into.websoso.domain.library.model.ReadStatuses.Companion.toReadStatuses
import org.junit.Assert.*
import org.junit.Test

class ReadStatusesTest {

@Test
fun `기본 상태에서는 아무 상태도 선택되지 않는다`() {
val statuses = ReadStatuses()

assertFalse(statuses.isSelected)
assertTrue(statuses.selectedKeys.isEmpty())
assertTrue(statuses.selectedLabels.isEmpty())

for (status in ReadStatus.entries) {
assertFalse(statuses[status])
}
}

@Test
fun `읽기 상태를 선택하면 해당 상태가 선택된다`() {
val statuses = ReadStatuses()
.set(ReadStatus.WATCHING)

assertTrue(statuses[ReadStatus.WATCHING])
assertTrue(statuses.isSelected)
assertEquals(listOf("WATCHING"), statuses.selectedKeys)
}

@Test
fun `같은 읽기 상태를 다시 선택하면 선택이 해제된다`() {
val statuses = ReadStatuses()
.set(ReadStatus.WATCHING)
.set(ReadStatus.WATCHING)

assertFalse(statuses[ReadStatus.WATCHING])
assertFalse(statuses.isSelected)
}

@Test
fun `문자열 키 목록으로 읽기 상태를 생성할 수 있다`() {
val keys = listOf("WATCHING", "QUIT", "UNKNOWN")

val statuses = keys.toReadStatuses()

assertTrue(statuses[ReadStatus.WATCHING])
assertTrue(statuses[ReadStatus.QUIT])
assertFalse(statuses[ReadStatus.WATCHED])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.into.websoso.domain.library.model

import org.junit.Assert.assertEquals
import org.junit.Test

class SortCriteriaTest {

@Test
fun `정렬 키가 일치하면 해당 정렬 기준을 반환한다`() {
assertEquals(SortCriteria.RECENT, SortCriteria.from("RECENT"))
assertEquals(SortCriteria.OLD, SortCriteria.from("OLD"))
}

@Test
fun `정렬 키가 일치하지 않으면 최신순으로 처리된다`() {
assertEquals(SortCriteria.RECENT, SortCriteria.from("UNKNOWN"))
assertEquals(SortCriteria.RECENT, SortCriteria.from(""))
assertEquals(SortCriteria.RECENT, SortCriteria.from("recent"))
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ paging = "3.3.6"
junit = "4.13.2"
androidx-test-junit = "1.3.0"
espresso-core = "3.7.0"
mockk = "1.13.10"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

MockK latest version 2024 2025 Maven Central

💡 Result:

For Maven Central artifact io.mockk:mockk:

  • Latest overall (as of Feb 24, 2026): 1.14.9 (released Jan 26, 2026) [1]
  • Latest released in 2025: 1.14.7 (released Dec 6, 2025) [2]
  • Latest released in 2024: 1.13.14 (released Dec 20, 2024) [2]

The same latest version (1.14.9) is also shown for related artifacts like io.mockk:mockk-jvm and io.mockk:mockk-agent-jvm on Maven Central mirrors/indexes. [3] [4]

Sources: [1] [2] [3] [4]


🌐 Web query:

MockK 1.14.9 breaking changes Android unit testing

💡 Result:

MockK 1.14.9 doesn’t introduce Android-specific API breakage, but it can break Android unit-test builds in a couple of practical ways:

  1. JUnit is no longer pulled in transitively (breaking for some builds)
  • MockK changed its JUnit 4/5 dependencies from implementation to compileOnly, so your test runtime may no longer have JUnit unless you declare it yourself. This was done because MockK’s transitive JUnit deps could cause tooling (e.g., Maven Surefire) to auto-detect JUnit 5 and skip JUnit 4 tests. [1], [2]
    Fix (Gradle examples):
testImplementation("junit:junit:4.13.2") // if using MockKRule / JUnit4
// or
testImplementation("org.junit.jupiter:junit-jupiter:5.x.x") // if using MockKExtension / JUnit5

(Use the appropriate one for your test framework.)

  1. Settings-file precedence changed
  • MockK now prioritizes mockk.properties in its settings resolution, which can change behavior if you previously relied on another settings file being picked up first. [1]

Sources:
[1] MockK v1.14.9 release notes (GitHub) (github.com)
[2] PR #1455 (“Change JUnit 4/5 dependencies from implementation to compileOnly”) (github.com)

Citations:


🌐 Web query:

MockK version 1.14.x changelog breaking changes

💡 Result:

MockK 1.14.x — notable “breaking” / migration-relevant changes

  • 1.14.2 (Apr 29, 2025): MockK config file locations moved to test source set

    • Docs/config guidance changed from src/main/resourcessrc/test/resources for io/mockk/settings.properties, and includes mockk.properties under test resources. If you previously shipped these in main, you’ll need to relocate them for tests. [2]
  • 1.14.5 (Jul 16, 2025): BDD aliases moved into separate artifacts

    • BDD-style aliases (given, then, etc.) are provided via new modules mockk-bdd / mockk-bdd-android; using those APIs now requires adding those dependencies. [3]
  • 1.14.9 (Jan 26, 2026): JUnit no longer comes as a transitive dependency

    • MockK changed JUnit 4/5 dependencies from implementation to compileOnly, which can break builds/tests that relied on MockK bringing JUnit onto the classpath transitively. [1]

Sources: GitHub releases / PRs for MockK. [1] [2] [3]


MockK를 1.14.9로 업그레이드 시 JUnit 의존성 선언 필수

최신 버전 1.14.9는 Maven Central에서 2026년 1월에 릴리스되었습니다. 다만 이 버전으로 업그레이드할 때는 다음의 breaking change를 고려해야 합니다:

  • JUnit이 더 이상 transitive dependency로 제공되지 않습니다 (1.14.9): MockK이 JUnit 4/5 의존성을 implementation에서 compileOnly로 변경했으므로, JUnit을 명시적으로 선언해야 합니다.

업그레이드 시 build.gradle에 아래와 같이 JUnit을 추가로 선언하세요:

testImplementation("junit:junit:4.13.2")  // JUnit 4 사용 시
// 또는
testImplementation("org.junit.jupiter:junit-jupiter:5.x.x")  // JUnit 5 사용 시

이외에도 1.14.x 버전에서는 설정 파일 위치 변경(src/test/resources로 이동)과 BDD alias 분리 등의 변경사항이 있으니 릴리스 노트를 참고하세요.

♻️ 버전 업그레이드 제안
-mockk = "1.13.10"
+mockk = "1.14.9"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gradle/libs.versions.toml` at line 41, libs.versions.toml currently pins
MockK at "1.13.10"; when you upgrade the mockk entry to "1.14.9" be sure to add
an explicit JUnit test dependency because MockK 1.14.9 no longer brings JUnit
transitively: update build.gradle to include either
testImplementation("junit:junit:4.13.2") for JUnit4 or
testImplementation("org.junit.jupiter:junit-jupiter:<version>") for JUnit5, and
verify test resource locations and any BDD alias changes per the 1.14.x release
notes.


# Networking Libraries
retrofit = "3.0.0"
Expand Down Expand Up @@ -111,6 +112,7 @@ paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pag
junit = { module = "junit:junit", version.ref = "junit" }
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }

# Networking Libraries
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
Expand Down