Skip to content

Commit

Permalink
Avoid spying collection-like types with @SPY if it may mislead @Injec…
Browse files Browse the repository at this point in the history
…tmocks behaviour (#2642)

* Fix bug with injecting of different types of thisInstance fields using @SPY.

---------

Co-authored-by: Egor Kulikov <[email protected]>
  • Loading branch information
tepa46 and EgorkaKulikov authored Oct 4, 2023
1 parent 09aebe1 commit 14fc1ea
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,10 @@ fun UtModel.isMockModel() = this is UtCompositeModel && isMock
* Checks that this [UtModel] must be constructed with @Spy annotation in generated tests.
* Used only for construct variables with @Spy annotation.
*/
fun UtModel.canBeSpied(): Boolean {
val javaClass = this.classId.jClass
fun UtModel.canBeSpied(): Boolean =
this is UtAssembleModel && spiedTypes.any { type -> type.isAssignableFrom(this.classId.jClass)}

return this is UtAssembleModel &&
(Collection::class.java.isAssignableFrom(javaClass)
|| Map::class.java.isAssignableFrom(javaClass))
}
val spiedTypes = setOf(Collection::class.java, Map::class.java)

/**
* Get model id (symbolic null value for UtNullModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.codegen.tree.fieldmanager

import org.utbot.framework.codegen.domain.UtModelWrapper
import org.utbot.framework.codegen.domain.builtin.spyClassId
import org.utbot.framework.codegen.domain.context.CgContext
import org.utbot.framework.codegen.domain.models.CgFieldDeclaration
Expand All @@ -12,6 +13,8 @@ import org.utbot.framework.plugin.api.UtAssembleModel
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.canBeSpied
import org.utbot.framework.plugin.api.isMockModel
import org.utbot.framework.plugin.api.spiedTypes
import org.utbot.framework.plugin.api.util.jClass

class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) {

Expand All @@ -38,10 +41,10 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con
cgModel !in dependentMockModels
}

return constructFieldsWithAnnotation(dependentSpyModels)
val suitableSpyModels = getSuitableSpyModels(dependentSpyModels)
return constructFieldsWithAnnotation(suitableSpyModels)
}


private val spyFrameworkManager = SpyFrameworkManager(context)

override fun useVariableForModel(model: UtModel, variable: CgValue) {
Expand All @@ -57,4 +60,29 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con
classId.canBeInjectedByTypeInto(classUnderTest)

override fun constructBaseVarName(model: UtModel): String = super.constructBaseVarName(model) + "Spy"

private fun getSuitableSpyModels(potentialSpyModels: MutableSet<UtModelWrapper>): Set<UtModelWrapper> =
spiedTypes.fold(setOf()) { spyModels, type ->
spyModels + getSuitableSpyModelsOfType(type, potentialSpyModels)
}

/*
* Filters out cases when different tests use different [clazz]
* implementations and hence we need to inject different types.
*
* This limitation is reasoned by @InjectMocks behaviour.
* Otherwise, injection may be misleading:
* for example, several spies may be injected into one field.
*/
private fun getSuitableSpyModelsOfType(
clazz: Class<*>,
potentialSpyModels: MutableSet<UtModelWrapper>
): Set<UtModelWrapper> {
val spyModelsAssignableFrom = potentialSpyModels
.filter { clazz.isAssignableFrom(it.model.classId.jClass) }
.toSet()
val spyModelsTypesCount = spyModelsAssignableFrom.map { it.model.classId }.toSet().size

return if (spyModelsTypesCount == 1) spyModelsAssignableFrom else emptySet()
}
}

0 comments on commit 14fc1ea

Please sign in to comment.