I'm observing different behaviour in two similar Grails 3.3.5 test cases. The difference between the two cases is:
objects1
and objects2
. When the object is modified in the spec, the changes apply to both, objects1
and objects2
.objects1
) and then modify this object in the spec, the changes are only applied to objects1
, but not to the other collection objects2
.My question is: according to the object oriented paradigm of Java and Groovy, I would expect both tests to behave like the first test. Why is there a difference, depending on which class is creating the object?
For clarification, my code example follows here. The domain class:
class MyTestObject {
String value
static constraints = {
}
MyTestObject(){
value = "initialized"
}
void edit(){
value = "edited"
}
String getValue(){
value
}
}
The controller class:
class MyTestController {
def index() { }
Map<Integer, MyTestObject> objects1
Map<Integer, MyTestObject> objects2
MyTestController(){
objects1 = new HashMap<>()
objects2 = new HashMap<>()
}
void addObject(){
int count = objects1.size()
objects1.put(count, new MyTestObject())
objects2.put(count, new MyTestObject())
}
void addObject(MyTestObject testObject){
int count = objects1.size()
objects1.put(count, testObject)
objects2.put(count, testObject)
}
MyTestObject getObjectFromCollection1(int atPosition){
if (0 > atPosition || atPosition > objects1.size()-1){
return
}
objects1.get(atPosition)
}
MyTestObject getObjectFromCollection2(int atPosition){
if (0 > atPosition || atPosition > objects2.size()-1){
return
}
objects2.get(atPosition)
}
void updateObjectInCollection1(int index, MyTestObject object){
objects1.put(index, object)
}
}
The spec is:
import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification
class MyTestControllerSpec extends Specification implements ControllerUnitTest<MyTestController> {
int index
def setup(){
index = 0
}
void "case 1: external object instantiation"() {
MyTestController controllerLocal = new MyTestController()
MyTestObject object = new MyTestObject()
controllerLocal.addObject(object)
object.edit()
MyTestObject afterEditInCollection1 = controllerLocal.getObjectFromCollection1(index)
MyTestObject afterEditInCollection2 = controllerLocal.getObjectFromCollection2(index)
expect:
"edited" == object.getValue()
"edited" == afterEditInCollection1.getValue()
"edited" == afterEditInCollection2.getValue()
}
void "case 2: internal object instantiation"() {
MyTestController controllerLocal = new MyTestController()
controllerLocal.addObject()
MyTestObject toBeEditedLocally = controllerLocal.getObjectFromCollection1(index)
toBeEditedLocally.edit()
MyTestObject afterEditInCollection1 = controllerLocal.getObjectFromCollection1(index)
MyTestObject afterEditInCollection2 = controllerLocal.getObjectFromCollection2(index)
expect:
"edited" == toBeEditedLocally.getValue()
"edited" == afterEditInCollection1.getValue()
"edited" == afterEditInCollection2.getValue()
}
def cleanup() {
}
}
Why is there a difference, depending on which class is creating the object?
There isn't a difference depending on which class is creating the object. The issue doesn't have to do with where the instances are created. The issue is that in addObject()
you are creating 2 separate objects, adding one of them to objects1
and the other to objects2
. In addObject(MyTestObject)
you are adding the same MyTestObject
instance to each of the 2 Map
.
void addObject(){
int count = objects1.size()
objects1.put(count, new MyTestObject())
objects2.put(count, new MyTestObject())
}
void addObject(MyTestObject testObject){
int count = objects1.size()
objects1.put(count, testObject)
objects2.put(count, testObject)
}
If you change addObject()
to look like this, your tests will each pass:
void addObject(){
int count = objects1.size()
def newTestObject = new MyTestObject()
objects1.put(count, newTestObject)
objects2.put(count, newTestObject)
}