Skip to content

Commit 5891851

Browse files
committed
Improve mock method result accessed error
1 parent 6edd7f5 commit 5891851

4 files changed

Lines changed: 41 additions & 30 deletions

File tree

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/MokkeryIr.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ object MokkeryIr {
7373
val runTemplate by dev_mokkery_internal_templating.refFunction
7474
val runTemplateSuspend by dev_mokkery_internal_templating.refFunction
7575
val templatingFunctionParameter by dev_mokkery_internal_templating.refFunction
76-
val checkNotMock by dev_mokkery_internal_templating.refFunction
76+
val checkMockMethodCallResultAccess by dev_mokkery_internal_templating.refFunction
7777
val MokkerySuiteScope by dev_mokkery.refFunction
7878
val createInstanceScope by dev_mokkery_internal.refFunction
7979
val createInstanceContext by dev_mokkery_internal.refFunction

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/templating/TemplatingTransformer.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ class TemplatingTransformer(
7777
private val defaultValuesMatcherConstructor = referencedPrimaryConstructor(MokkeryIr.Class.DefaultValuesMatcher)
7878
private val runTemplateBlockingFun = referenced(MokkeryIr.Function.runTemplate)
7979
private val runTemplateSuspendFun = referenced(MokkeryIr.Function.runTemplateSuspend)
80-
private val checkNotMockFun = referenced(MokkeryIr.Function.checkNotMock)
8180
private val inlineLiteralsAsMatchersFun = referenced(MokkeryIr.Function.inlineLiteralsAsMatchers)
8281

8382
private val contextFunctions = setOf(Mokkery.Name.ext, Mokkery.Name.ctx)
@@ -179,8 +178,12 @@ class TemplatingTransformer(
179178
override fun visitCall(expression: IrCall) = expression.transformPostfix {
180179
val dispatcher = dispatchReceiver ?: return@transformPostfix
181180
dispatchReceiver = dispatcher.replaceDeclarationIrBuilder {
182-
irCall(checkNotMockFun, type = dispatcher.type) {
181+
irCall(
182+
func = referenced(MokkeryIr.Function.checkMockMethodCallResultAccess),
183+
type = dispatcher.type
184+
) {
183185
arguments[0] = dispatcher
186+
arguments[1] = irString(expression.symbol.owner.name.asString())
184187
typeArguments[0] = dispatcher.type
185188
}
186189
}

mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/templating/RunTemplate.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ internal sealed interface RunTemplateResult<out T> {
1616

1717
val value: T
1818

19-
data class Original<T>(override val value: T): RunTemplateResult<T>
19+
data class Original<T>(override val value: T) : RunTemplateResult<T>
2020

21-
data object Empty : RunTemplateResult<Nothing> {
21+
data class Empty(val obj: Any?, val functionName: String) : RunTemplateResult<Nothing> {
2222
override val value: Nothing
23-
get() = mockMethodCallResultAccessError()
23+
get() = mockMethodCallResultAccessError(obj, functionName)
2424
}
2525
}
2626

27-
internal fun <T> checkNotMock(obj: T): T {
28-
if (obj.isMock) mockMethodCallResultAccessError()
27+
internal fun <T> checkMockMethodCallResultAccess(obj: T, functionName: String): T {
28+
if (obj.isMock) mockMethodCallResultAccessError(obj, functionName)
2929
return obj
3030
}
3131

@@ -55,7 +55,7 @@ internal suspend fun <R> MokkeryTemplatingScope.runTemplateSuspend(
5555
): RunTemplateResult<R> = when {
5656
mock.isMock -> {
5757
templatingRegistry.register(mock, functionName, arguments?.invoke(mock, mockedType).orEmpty())
58-
RunTemplateResult.Empty
58+
RunTemplateResult.Empty(mock, functionName)
5959
}
6060
original == null -> mockExpectedError(mock, mockedType, functionName, arguments?.invoke(mock, mockedType).orEmpty())
6161
else -> RunTemplateResult.Original(original())
@@ -70,7 +70,7 @@ internal fun <R> MokkeryTemplatingScope.runTemplate(
7070
): RunTemplateResult<R> = when {
7171
mock.isMock -> {
7272
templatingRegistry.register(mock, functionName, arguments?.invoke(mock, mockedType).orEmpty())
73-
RunTemplateResult.Empty
73+
RunTemplateResult.Empty(mock, functionName)
7474
}
7575
original == null -> mockExpectedError(mock, mockedType, functionName, arguments?.invoke(mock, mockedType).orEmpty())
7676
else -> RunTemplateResult.Original(original())
@@ -87,10 +87,13 @@ private fun mockExpectedError(
8787
}
8888

8989
@Suppress("NOTHING_TO_INLINE")
90-
private inline fun mockMethodCallResultAccessError(): Nothing {
90+
private inline fun mockMethodCallResultAccessError(obj: Any?, functionName: String): Nothing {
9191
mokkeryRuntimeError(
92-
"The result of a mock method must not be accessed inside `every` or `verify`." +
93-
" If you're trying to invoke a method with an extension receiver or context parameters," +
94-
" use the `dev.mokkery.templating.ext` or `dev.mokkery.templating.ctx` functions instead."
92+
"""
93+
The result of calling `$functionName` on $obj must not be accessed inside `every` or `verify`.
94+
95+
If you're trying to mock a method with an extension receiver or context parameters, use `dev.mokkery.templating.ext` or `dev.mokkery.templating.ctx` instead of Kotlin scope functions (e.g. `let`, `run`).
96+
Otherwise, using scope functions here is not supported.
97+
""".trimIndent()
9598
)
9699
}

test-mokkery/src/commonTest/kotlin/dev/mokkery/test/TemplatingTest.kt

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class TemplatingTest {
3434

3535
@Test
3636
fun testFailsAccessingMockCallResultInVariable() {
37-
assertFailsWithResultAccessError {
37+
assertFailsWithResultAccessError(mock, "callPrimitive") {
3838
verify {
3939
val variable = mock.callPrimitive(0)
4040
}
@@ -43,7 +43,7 @@ class TemplatingTest {
4343

4444
@Test
4545
fun testFailsWhenAccessingMockCallResultInWhen() {
46-
assertFailsWithResultAccessError {
46+
assertFailsWithResultAccessError(mock, "callPrimitive") {
4747
verify {
4848
when (true) {
4949
true -> mock.callPrimitive(1)
@@ -56,7 +56,7 @@ class TemplatingTest {
5656

5757
@Test
5858
fun testFailsWhenAccessingMockCallResultInNestedFunction() {
59-
assertFailsWithResultAccessError {
59+
assertFailsWithResultAccessError(mock, "callPrimitive") {
6060
verify {
6161
fun nested() = mock.callPrimitive(1)
6262
nested()
@@ -67,7 +67,7 @@ class TemplatingTest {
6767

6868
@Test
6969
fun testFailsWhenWrappingMockCallInScopeFunction() {
70-
assertFailsWithResultAccessError {
70+
assertFailsWithResultAccessError(mock, "callPrimitive") {
7171
every {
7272
1.let { mock.callPrimitive(it) }
7373
}
@@ -76,7 +76,7 @@ class TemplatingTest {
7676

7777
@Test
7878
fun testFailsWhenAccessingMockCallResultInAnotherCall() {
79-
assertFailsWithResultAccessError {
79+
assertFailsWithResultAccessError(mock, "callPrimitive") {
8080
verify {
8181
listOf(mock.callPrimitive(1))
8282
}
@@ -85,7 +85,7 @@ class TemplatingTest {
8585

8686
@Test
8787
fun testFailsWhenAccessingMockCallResultInCondition() {
88-
assertFailsWithResultAccessError {
88+
assertFailsWithResultAccessError(mock, "callPrimitive") {
8989
verify {
9090
if (mock.callPrimitive(1) == 1) return@verify
9191
}
@@ -94,7 +94,7 @@ class TemplatingTest {
9494

9595
@Test
9696
fun testFailsWhenAccessingMockCallResultAsIfCondition() {
97-
assertFailsWithResultAccessError {
97+
assertFailsWithResultAccessError(mock, "callBoolean") {
9898
verify {
9999
if (mock.callBoolean(false)) return@verify
100100
}
@@ -103,7 +103,7 @@ class TemplatingTest {
103103

104104
@Test
105105
fun testFailsWhenAccessingMockCallResultAsLoopCondition() {
106-
assertFailsWithResultAccessError {
106+
assertFailsWithResultAccessError(mock, "callBoolean") {
107107
verify {
108108
while (mock.callBoolean(false)) return@verify
109109
}
@@ -112,7 +112,7 @@ class TemplatingTest {
112112

113113
@Test
114114
fun testFailsWhenPassingMockCallResultToOtherMock() {
115-
assertFailsWithResultAccessError {
115+
assertFailsWithResultAccessError(mock, "callPrimitive") {
116116
verify {
117117
mock.callPrimitive(mock.callPrimitive(1))
118118
}
@@ -122,7 +122,7 @@ class TemplatingTest {
122122

123123
@Test
124124
fun testFailsWhenPassingNestedMockCallResultToOtherMethodCall() {
125-
assertFailsWithResultAccessError {
125+
assertFailsWithResultAccessError(mock, "callPrimitive") {
126126
val list = listOf<Int>()
127127
verify {
128128
mock.callPrimitive(list.getOrElse(mock.callPrimitive(1)) { 0 })
@@ -132,7 +132,7 @@ class TemplatingTest {
132132

133133
@Test
134134
fun testFailsWhenPassingMockCallResultToMethodCall() {
135-
assertFailsWithResultAccessError {
135+
assertFailsWithResultAccessError(mock, "callPrimitive") {
136136
val list = listOf<Int>()
137137
verify {
138138
list[mock.callPrimitive(1)]
@@ -161,9 +161,17 @@ class TemplatingTest {
161161
}
162162
}
163163

164-
private fun assertFailsWithResultAccessError(block: () -> Unit) {
164+
private fun assertFailsWithResultAccessError(obj: Any, functionName: String, block: () -> Unit) {
165165
val error = assertFailsWith<MokkeryRuntimeException> { block() }
166-
assertEquals(mockResultAccessError, error.message)
166+
assertEquals(
167+
"""
168+
The result of calling `$functionName` on $obj must not be accessed inside `every` or `verify`.
169+
170+
If you're trying to mock a method with an extension receiver or context parameters, use `dev.mokkery.templating.ext` or `dev.mokkery.templating.ctx` instead of Kotlin scope functions (e.g. `let`, `run`).
171+
Otherwise, using scope functions here is not supported.
172+
""".trimIndent(),
173+
error.message
174+
)
167175
}
168176

169177
private interface SelfType {
@@ -173,7 +181,4 @@ class TemplatingTest {
173181
fun callWithListSelf(self: List<SelfType>): List<SelfType>
174182
}
175183

176-
private val mockResultAccessError = "The result of a mock method must not be accessed inside `every` or `verify`." +
177-
" If you're trying to invoke a method with an extension receiver or context parameters," +
178-
" use the `dev.mokkery.templating.ext` or `dev.mokkery.templating.ctx` functions instead."
179184
}

0 commit comments

Comments
 (0)