Skip to content

Commit 71a71e1

Browse files
Call doNothing from mockito in tests for method calls returning void (#2016)
* Call doNothing from mockito in tests for method calls returning void * Disable flaky test in summaries * Fix broken & unnecessary reflection * Little style corrections * Introduced genericObjectClassId & applied comment fix suggestions --------- Co-authored-by: Andrey Tarbeev <54685068+sofurihafe@users.noreply.github.com>
1 parent 80c0e68 commit 71a71e1

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoBuiltins.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ internal val ongoingStubbingClassId = BuiltinClassId(
3535
simpleName = "OngoingStubbing",
3636
)
3737

38+
internal val stubberClassId = BuiltinClassId(
39+
canonicalName = "org.mockito.stubbing.Stubber",
40+
simpleName = "Stubber"
41+
)
42+
43+
// We wrap an Object classId in BuiltinClassId
44+
// in order to deal with [CgExpression.canBeReceiverOf]
45+
// when we want to call ANY method on an object.
46+
internal val genericObjectClassId = BuiltinClassId(
47+
canonicalName = "java.lang.Object",
48+
simpleName = "Object"
49+
)
50+
3851
internal val answerClassId = BuiltinClassId(
3952
canonicalName = "org.mockito.stubbing.Answer",
4053
simpleName = "Answer",
@@ -83,6 +96,19 @@ internal val thenReturnMethodId = builtinMethodId(
8396
arguments = arrayOf(objectClassId)
8497
)
8598

99+
internal val doNothingMethodId = builtinStaticMethodId(
100+
classId = mockitoClassId,
101+
name = "doNothing",
102+
returnType = stubberClassId,
103+
)
104+
105+
internal val whenStubberMethodId = builtinMethodId(
106+
classId = stubberClassId,
107+
name = "when",
108+
returnType = genericObjectClassId,
109+
arguments = arrayOf(objectClassId)
110+
)
111+
86112
// TODO: for this method and other static methods implement some utils that allow calling
87113
// TODO: these methods without explicit declaring class id specification, because right now such calls are too verbose
88114
internal val any = builtinStaticMethodId(

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.utbot.framework.codegen.domain.builtin.getMethodId
99
import org.utbot.framework.codegen.domain.builtin.getTargetException
1010
import org.utbot.framework.codegen.domain.builtin.invoke
1111
import org.utbot.framework.codegen.domain.builtin.newInstance
12+
import org.utbot.framework.codegen.domain.builtin.genericObjectClassId
1213
import org.utbot.framework.codegen.domain.builtin.setAccessible
1314
import org.utbot.framework.codegen.domain.context.CgContext
1415
import org.utbot.framework.codegen.domain.context.CgContextOwner
@@ -190,13 +191,25 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
190191

191192
private infix fun CgExpression.canBeReceiverOf(executable: MethodId): Boolean =
192193
when {
193-
// method of the current test class can be called on its 'this' instance
194+
// this executable can be called on its 'this' instance
194195
currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true
195-
// method of a class can be called on an object of this class or any of its subtypes
196+
197+
// this executable can be called on an object of this class or any of its subtypes
196198
this.type isSubtypeOf executable.classId -> true
199+
200+
// this executable can be called on builtin type
201+
this.type is BuiltinClassId && this.type in builtinCallersWithoutReflection -> true
202+
197203
else -> false
198204
}
199205

206+
// For some builtin types we need to clarify
207+
// that it is allowed to call an executable without reflection.
208+
//
209+
// This approach is used, for example, to render the constructions with stubs
210+
// like `doNothing().when(entityManagerMock).persist(any())`.
211+
private val builtinCallersWithoutReflection = setOf(genericObjectClassId)
212+
200213
/**
201214
* For Kotlin extension functions, real caller is one of the arguments in JVM method (and declaration class is omitted),
202215
* thus we should move it from arguments to caller

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import org.utbot.framework.codegen.domain.builtin.anyLong
1313
import org.utbot.framework.codegen.domain.builtin.anyOfClass
1414
import org.utbot.framework.codegen.domain.builtin.anyShort
1515
import org.utbot.framework.codegen.domain.builtin.argumentMatchersClassId
16+
import org.utbot.framework.codegen.domain.builtin.doNothingMethodId
1617
import org.utbot.framework.codegen.domain.builtin.mockMethodId
1718
import org.utbot.framework.codegen.domain.builtin.mockedConstructionContextClassId
1819
import org.utbot.framework.codegen.domain.builtin.mockitoClassId
1920
import org.utbot.framework.codegen.domain.builtin.thenReturnMethodId
2021
import org.utbot.framework.codegen.domain.builtin.whenMethodId
22+
import org.utbot.framework.codegen.domain.builtin.whenStubberMethodId
2123
import org.utbot.framework.codegen.domain.context.CgContext
2224
import org.utbot.framework.codegen.domain.context.CgContextOwner
2325
import org.utbot.framework.codegen.domain.models.CgAnonymousFunction
@@ -190,28 +192,39 @@ private class MockitoMocker(context: CgContext) : ObjectMocker(context) {
190192

191193
fun mockForVariable(model: UtCompositeModel, mockObject: CgVariable) {
192194
for ((executable, values) in model.mocks) {
193-
// void method
194-
if (executable.returnType == voidClassId) {
195-
// void methods on mocks do nothing by default
196-
continue
197-
}
195+
val matchers = mockitoArgumentMatchersFor(executable)
198196

199-
when (executable) {
200-
is MethodId -> {
201-
if (executable.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) {
202-
error("Cannot mock method $executable with not accessible parameters" )
197+
if (executable.returnType == voidClassId) {
198+
when (executable) {
199+
// All constructors are considered like void methods, but it is proposed to be changed to test constructors better.
200+
is ConstructorId -> continue
201+
// Sometimes void methods are called explicitly, e.g. annotated with @Mock fields in Spring test classes.
202+
// We would like to mark that this field is used and must not be removed from test class.
203+
// Without `doNothing` call Intellij Idea suggests removing this field as unused.
204+
is MethodId -> {
205+
+mockitoClassId[doNothingMethodId]()[whenStubberMethodId](mockObject)[executable](*matchers)
203206
}
207+
else -> error("Only MethodId and ConstructorId was expected to appear in simple mocker but got $executable")
208+
}
209+
} else {
210+
when (executable) {
211+
is MethodId -> {
212+
if (executable.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) {
213+
error("Cannot mock method $executable with not accessible parameters" )
214+
}
204215

205-
val matchers = mockitoArgumentMatchersFor(executable)
206-
if (!executable.isAccessibleFrom(testClassPackageName)) {
207-
error("Cannot mock method $executable as it is not accessible from package $testClassPackageName")
208-
}
216+
if (!executable.isAccessibleFrom(testClassPackageName)) {
217+
error("Cannot mock method $executable as it is not accessible from package $testClassPackageName")
218+
}
209219

210-
val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray()
211-
`when`(mockObject[executable](*matchers)).thenReturn(executable.returnType, *results)
220+
val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray()
221+
`when`(mockObject[executable](*matchers)).thenReturn(executable.returnType, *results)
222+
}
223+
else -> error("Only MethodId was expected to appear in simple mocker but got $executable")
212224
}
213-
else -> error("ConstructorId was not expected to appear in simple mocker but got $executable")
214225
}
226+
227+
215228
}
216229
}
217230

0 commit comments

Comments
 (0)