Search code examples
javaunit-testinggrails

Grails instance modification in collection


I'm observing different behaviour in two similar Grails 3.3.5 test cases. The difference between the two cases is:

  • In the first test case, the spec creates a test object, hands it to the controller who adds / puts it to his two collections objects1 and objects2. When the object is modified in the spec, the changes apply to both, objects1 and objects2.
  • In the second test case, the controller itself creates an object and puts it in his two collections. When I ask the controller to deliver the object from the first collection (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() {
    }

}

Solution

  • 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)
    }