Creating Test Data With Groovy
After we have finished this lesson, we:
- Can create new objects by using named argument constructors.
- Know how we can set the field values of an object.
This lesson assumes that:
Creating New Objects
If the instantiated class has no constructor or it has a constructor that has no constructor arguments, we can use a so called named argument constructor. We can specify the field values of the created object by using key/value pairs, and the constructor creates a new object and sets the field values of the created object.
Let’s take a look at a simple example that demonstrates how named argument constructors work.
The TaskFormDTO class is a simple data transfer object that contains the information of the created or updated task. It has three fields which contain the id, description, and title of the task. Also, it has a no-argument constructor.
The source code of the TaskFormDTO class looks as follows:
1public class TaskFormDTO {
2
3 private Long id;
4
5 private String description;
6
7 private String title;
8
9 public TaskFormDTO() {
10
11 }
12
13 public Long getId() {
14 return id;
15 }
16
17 public String getDescription() {
18 return description;
19 }
20
21 public String getTitle() {
22 return title;
23 }
24
25 public void setId(Long id) {
26 this.id = id;
27 }
28
29 public void setDescription(String description) {
30 this.description = description;
31 }
32
33 public void setTitle(String title) {
34 this.title = title;
35 }
36}
Let’s create a new TaskFormDTO object and specify the field values of the id, title, and description fields by using a named argument constructor. After we have created a new TaskFormDTO object, the source code of our specification class looks as follows:
1import org.junit.experimental.categories.Category
2import spock.lang.Specification
3
4@Category(UnitTest.class)
5class ConstructorSpec extends Specification {
6
7 def 'Create a new object by using the named argument constructor'() {
8
9 when: 'A new task form object is created'
10 def object = new TaskFormDTO(id: 1L,
11 title: 'title',
12 description: 'description'
13 )
14
15 then: 'Should create a new object with correct information'
16 object.id == 1L
17 object.title == 'title'
18 object.description == 'description'
19 }
20}
Additional Reading:
As we can see, named argument constructors can help us to save a few lines of code if we want to pass only a few field values. Also, because the constructor arguments have names, our test code is easy to read.
That being said, we shouldn’t use this technique if we are creating stateful objects because named constructor arguments don’t help us to describe the meaning of different property values and this techniques forces us to scatter the object creation logic to our specification classes. In other words, if we use named argument constructors for this purpose, we write tests that are hard to read, write, and maintain.
Let’s move on and find out how we can set the field values of an object.
Setting the Field Values of an Object
If we want to set the field values of an object, we can use either properties, direct field access, or reflection. Next, we will find out how we can set the field values of an object by using properties.
Using Properties
A property is basically a combination of a private field and a getter or a setter. We can access a property by using its name and the appropriate getter or setter is called transparently.
Let’s create a new TaskFormDTO object and set the values of the id, title, and description fields by using properties. After we have created a new object, the source code of our specification class looks as follows:
1import org.junit.experimental.categories.Category
2import spock.lang.Specification
3
4@Category(UnitTest.class)
5class PropertySpec extends Specification {
6
7 def 'Create a new object and set its property values'() {
8
9 when: 'We create a new object and set its property values'
10 def object = new TaskFormDTO()
11 object.id = 1L
12 object.title = 'title'
13 object.description = 'description'
14
15 then: 'Should create a new object and set its property values'
16 object.id == 1L
17 object.title == 'title'
18 object.description == 'description'
19 }
20}
If we are accessing a property “inside” the class that defines it by using the this keyword, our code will access the field and the getter or setter is not invoked.
Additional Reading:
Let’s move on and find out how we can set the field values of an object by using the direct field access operator.
Using the Direct Field Access Operator
If we want to access a field of an object and we cannot invoke the getter or setter (or we just don’t want to do it), we can access the field by using the direct field access operator (.@).
Let’s use this operator when we implement a test data builder that creates new Task objects. Because this builder is an exact copy of the builder that we created during the starter package
, I won’t describe its implementation here.
Our goal is to finish the implementation of the build() method. We can do this by setting the field values of the private fields that cannot be set by using the builder provided by the Task class. After we have set the field values of these fields, the source code of our builder class looks as follows:
1import java.time.ZonedDateTime
2
3class TaskBuilder {
4
5 private static final NOT_IN_USE = -1L
6 private static final NOT_IMPORTANT = 'NOT_IMPORTANT'
7 private static final NOT_SET = ZonedDateTime
8 .now()
9 .minusYears(9999)
10
11 private assigneeId = null
12 private closerId = null
13 private creationTime = NOT_SET
14 private creatorId = NOT_IN_USE
15 private description = NOT_IMPORTANT
16 private id = NOT_IN_USE
17 private modificationTime = NOT_SET
18 private modifierId = NOT_IN_USE
19 private resolution = null
20 private status = null
21 private tags = new HashSet<>()
22 private title = NOT_IMPORTANT
23
24 //The methods that set field values are omitted
25
26 def build() {
27 def task = Task.builder
28 .withCreator(creatorId)
29 .withDescription(description)
30 .withTitle(title)
31 .build()
32
33 if (assigneeId != null) {
34 task.@assignee = new Assignee(assigneeId)
35 }
36
37 if (closerId != null) {
38 task.@closer = new Closer(closerId)
39 }
40
41 if (modifierId != null) {
42 task.@modifier = new Modifier(modifierId)
43 }
44
45 task.@status = status
46 task.@resolution = resolution
47 task.@tags = tags
48
49 return task
50 }
51}
If we are trying to access the fields of a Java class, we don’t have to use the direct field access operator because we can use either properties or reflection. However, it can be useful if we are writing tests for Groovy code. If you want to get more details about this, you should take a look at the following links:
Additional Reading:
Our test data builder is not finished yet because we haven’t set the field values of the fields that are declared by the AbstractEntity class. Next, we will find out how we can set the field values of these fields.
Using Reflection
The Task class extends the AbstractEntity class which provides common fields that are shared by all entities of our application. The source code of the AbstractEntity class looks as follows:
1import javax.persistence.*;
2import java.time.ZonedDateTime;
3
4@MappedSuperclass
5public abstract class AbstractEntity {
6
7 @Id
8 @GeneratedValue(strategy = GenerationType.IDENTITY)
9 private Long id;
10
11 @Column(name = "creation_time", nullable = false)
12 private ZonedDateTime creationTime;
13
14 @Column(name = "modification_time")
15 private ZonedDateTime modificationTime;
16
17 @Version
18 private Long version;
19
20 //Getters are omitted
21}
As we can see, the AbstractEntity class declares four private fields that have getter methods but no setter methods. This means that we cannot set the field values of these fields by using properties. Also, because private fields are not inherited, we cannot set the values of these fields by using the direct field access operator. That’s why we have to use reflection.
If we want to set field values by using reflection, we have to follow these steps:
First, we have to implement a utility class that can set the field values of an object by using reflection. We can implement this class by following these steps:
- Create a
finalclass calledReflectionFieldSetter. - Add a
privateconstructor to the created class. - Add a
publicandstaticsetFieldValue()method to created class. This method takes the target object, the name of the field, and the field value as method parameters. - Implement the
setFieldValue()method. This method gets the target field, ensures that we can access the target field, and sets the field value of the target field.
After we have implemented this utility class, its source code looks as follows:
1final class ReflectionFieldSetter {
2
3 private ReflectionFieldSetter() {}
4
5 static def setFieldValue(target, fieldName, fieldValue) {
6 try {
7 def targetField = findFieldFromClassHierarchy(target.class, fieldName)
8 targetField.accessible = true
9 targetField.set(target, fieldValue)
10 }
11 catch (Exception ex) {
12 throw new RuntimeException(
13 "Cannot set the field value because of an error",
14 ex
15 )
16 }
17 }
18
19 private static def findFieldFromClassHierarchy(clazz, fieldName) {
20 Class<?> current = clazz
21
22 def continueSearch = true
23 while (continueSearch) {
24 try {
25 return current.getDeclaredField(fieldName);
26 }
27 catch(Exception e) {
28 //An exception simply means that field is not found.
29 }
30
31 current = current.superclass
32 continueSearch = current != null
33 }
34
35 throw new NoSuchFieldException(String.format(
36 "No field found with the field name %s",
37 fieldName
38 ))
39 }
40}
Second, we have to modify the build() method of the TaskBuilder class. This method must set the field values of the id, creationTime, and modificationTime fields by using our new utility class.
After we have made the required changes to the build() method, the source code of the TaskBuilder class looks as follows:
1import java.time.ZonedDateTime
2
3class TaskBuilder {
4
5 private static final NOT_IN_USE = -1L
6 private static final NOT_IMPORTANT = 'NOT_IMPORTANT'
7 private static final NOT_SET = ZonedDateTime.now().minusYears(9999)
8
9 private assigneeId = null
10 private closerId = null
11 private creationTime = NOT_SET
12 private creatorId = NOT_IN_USE
13 private description = NOT_IMPORTANT
14 private id = NOT_IN_USE
15 private modificationTime = NOT_SET
16 private modifierId = NOT_IN_USE
17 private resolution = null
18 private status = null
19 private tags = new HashSet<>()
20 private title = NOT_IMPORTANT
21
22 //The methods that set field values are omitted
23
24 def build() {
25 def task = Task.builder
26 .withCreator(creatorId)
27 .withDescription(description)
28 .withTitle(title)
29 .build()
30
31 if (assigneeId != null) {
32 task.@assignee = new Assignee(assigneeId)
33 }
34
35 if (closerId != null) {
36 task.@closer = new Closer(closerId)
37 }
38
39 ReflectionFieldSetter.setFieldValue(task,
40 'creationTime',
41 creationTime
42 )
43 ReflectionFieldSetter.setFieldValue(task, 'id', id)
44 ReflectionFieldSetter.setFieldValue(task,
45 'modificationTime',
46 modificationTime
47 )
48
49 if (modifierId != null) {
50 task.@modifier = new Modifier(modifierId)
51 }
52
53 task.@status = status
54 task.@resolution = resolution
55 task.@tags = tags
56
57 return task
58 }
59}
We can now create new objects by using the named argument constructor and set the field values of the created objects by using the syntactic sugar provided by the Groovy programming language.
Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us five things:
- Named argument constructors are useful if the created object is just a data container and we want to set only a few field values.
- When we access a field by using properties, the appropriate getter or setter is invoked transparently.
- The direct field access operator allows us to access field values without invoking getters or setters.
- If we want to set the field values of a superclass that has no setters and the fields are
private, we have to use reflection. - If we want to access the fields of a Java class, we can use either properties or reflection.