Field access in Groovy for super class

Viewed 4033

In Groovy there is an @ operator which enables direct field access. However it looks like it won't work for fields declared in super class. Consider two Java (not Groovy) classes:

class Entity {
  private Long id;

  Long getId() {
    return id;
  }
}

class User extends Entity {
}

Then invoking direct access in Groovy

User user = new User();
user.@id = 1L

ends up with exception: groovy.lang.MissingFieldException: No such field: id for class User

When I try to use standard access user.id = 1L I get groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: id for class User

Is there any option to access field declared in super class?

4 Answers

Let me revive this question as I'm going through a similar thing recently. Here are a few thoughts...

As it's needed for tests perhaps a comparison by equals would do instead of checking/setting fields directly?

Pattern worth considering if the alternative is de-encapsulation. I mean, if there is no need to touch these fields in prod code ideally they shouldn't need being touched in tests either.

Entity would have own fields based equals and hashCode implemented. Lombok's @EqualsAndHashCode annotation can help with reducing the boilerplate.

Test goes then like expect: new Entity(id) == new Entity(id) (assuming there is such constructor).

If equality is not the way to go package scope might give better encapsulation with an appropriate package structure (main/src/java):

package foo.bar

public class Entity {
    Long id
}

Control over the id access is more flexible now and independent from the test code. While User extends Entity can have no access to the id if placed in a different package, the test code can have a class accessing it, like for example (src/test/groovy):

package foo.bar

class EntityAccess {
    private final Entity entity

    EntityAccess(Entity entity) {
        this.entity = entity
    }

    static EntityAccess access(Entity entity) {
        new EntityAccess(entity)
    }

    Long getId() {
        entity.id
    }

    void setId(Long id) {
        entity.id = id
    }
}

I'm sure the boilerplate can be reduced with some fancy Groovy AST annotations. The point is prod code can keep the fields hidden while in tests, with a static import for EntityAccess.access, a field can be accessed without any Groovy hax0rs:

given: access(entity).id = 5 to set or expect: access(entity).id == 5 to assert on entity's id.

If possible though, I would've rather kept both the Entity and User immutable and tested by equality without altering the objects state.

Related