Creating Test Data With the New Keyword: The JavaBeans Style Construction
After we have finished this lesson, we
- Can identify the drawbacks of the JavaBeans style construction.
- Understand when we shouldn’t use the JavaBeans style construction in our test classes.
- Know when we should use the JavaBeans style construction in our test classes.
Using the new keyword is the only way to create new objects, and this lesson doesn’t criticize its existence. The point of this lesson is to show us that we must use it in the right place.
This lesson assumes that:
Get the source code from Github
Creating New Objects by Using the JavaBeans Style Construction
As we remember
, the Task class is a JavaBean. Let’s find out what happens when we create new Task objects and set the property values of these objects by using setter methods.
However, before we can create new Task objects, we have to create a new test class and add all required constants to our test class.
After we have created our test class, its source code looks as follows:
1public class TaskTest {
2
3 private static final Long ID = 1L;
4 private static final Long ASSIGNEE_ID = 99L;
5 private static final Long CLOSER_ID = 55L;
6 private static final Long CREATOR_ID = 44L;
7 private static final Long MODIFIER_ID = 66L;
8 private static final String TITLE = "Write an example project";
9 private static final String DESCRIPTION = "description";
10}
We are now ready to create new Task objects. Let’s create three different Task objects and evaluate what we learn from these examples.
First, if we want to create an open task that is not assigned to anyone, we have to set the property values of the following properties:
- Id
- Creator
- Modifier
- Title
- Description
- Status
After we have written a test method that creates an open task that is not assigned to anyone, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class TaskTest {
4
5 private static final Long ID = 1L;
6 private static final Long ASSIGNEE_ID = 99L;
7 private static final Long CLOSER_ID = 55L;
8 private static final Long CREATOR_ID = 44L;
9 private static final Long MODIFIER_ID = 66L;
10 private static final String TITLE = "Write an example project";
11 private static final String DESCRIPTION = "description";
12
13 @Test
14 public void createOpenTaskWithoutAssignee() {
15 Task task = new Task();
16 task.setId(ID);
17 task.setAssignee(null);
18 task.setCloser(null);
19 task.setCreator(new Creator(CREATOR_ID));
20 task.setModifier(new Modifier(MODIFIER_ID));
21 task.setDescription(DESCRIPTION);
22 task.setTitle(TITLE);
23 task.setStatus(TaskStatus.OPEN);
24 task.setResolution(null);
25 }
26}
This test method is easy to read because we set the property values of the created object by using setter methods which have proper names. However, the problem is that before we can create an open task that is not assigned to anyone, we have to know that:
- The status of an open task must be
TaskStatus.OPEN. - An open task cannot have a resolution.
- The closer of an open task must be
null. - The creator and modifier of the task must be specified.
- Because the task is not assigned to anyone, the assignee must be
null.
Second, if we want to create an open task that is assigned to a person, we have to set the property values of the following properties:
- Id
- Assignee
- Creator
- Modifier
- Title
- Description
- Status
After we have written a test method that creates an open task that is assigned to a person, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class TaskTest {
4
5 private static final Long ID = 1L;
6 private static final Long ASSIGNEE_ID = 99L;
7 private static final Long CLOSER_ID = 55L;
8 private static final Long CREATOR_ID = 44L;
9 private static final Long MODIFIER_ID = 66L;
10 private static final String TITLE = "Write an example project";
11 private static final String DESCRIPTION = "description";
12
13 @Test
14 public void createOpenTaskThatIsAssignedToPerson() {
15 Task task = new Task();
16 task.setId(ID);
17 task.setAssignee(new Assignee(ASSIGNEE_ID));
18 task.setCloser(null);
19 task.setCreator(new Creator(CREATOR_ID));
20 task.setModifier(new Modifier(MODIFIER_ID));
21 task.setDescription(DESCRIPTION);
22 task.setTitle(TITLE);
23 task.setStatus(TaskStatus.OPEN);
24 task.setResolution(null);
25 }
26}
Again, this test method is easy to read because the setter methods are named properly. That being said, the problem is that before we can create an open task that is assigned to a person, we have to know that:
- The status of an open task must be
TaskStatus.OPEN. - An open task cannot have a resolution.
- The closer of an open task must be
null. - The assignee, creator, and modifier of the task must be specified.
Third, if we want to create a task that was closed as a duplicate, we have to set the property values of the following properties:
- Id
- Assignee
- Closer
- Creator
- Modifier
- Title
- Description
- Status
- Resolution
After we have written a test method that creates a task that was closed as a duplicate, the source code of our test class looks as follows:
1import org.junit.Test;
2
3public class TaskTest {
4
5 private static final Long ID = 1L;
6 private static final Long ASSIGNEE_ID = 99L;
7 private static final Long CLOSER_ID = 55L;
8 private static final Long CREATOR_ID = 44L;
9 private static final Long MODIFIER_ID = 66L;
10 private static final String TITLE = "Write an example project";
11 private static final String DESCRIPTION = "description";
12
13 @Test
14 public void createTaskThatWasClosedAsDuplicate() {
15 Task task = new Task();
16 task.setId(ID);
17 task.setAssignee(new Assignee(ASSIGNEE_ID));
18 task.setCloser(new Closer(CLOSER_ID));
19 task.setCreator(new Creator(CREATOR_ID));
20 task.setModifier(new Modifier(MODIFIER_ID));
21 task.setDescription(DESCRIPTION);
22 task.setTitle(TITLE);
23 task.setStatus(TaskStatus.CLOSED);
24 task.setResolution(TaskResolution.DUPLICATE);
25 }
26}
This test method is easy to read, but the problem is that we have just increased the number of things we must remember. Before we can create a task which was closed as a duplicate, we have to know that:
- The status of a closed task must be
TaskStatus.CLOSED. - Because the task was closed as a duplicate, its resolution must be
TaskResolution.DUPLICATE. - The assignee, closer, creator, and modifier of the task must be specified.
These examples demonstrate that if we use the JavaBeans style construction, we write data-centric code. We simply create new objects and set the property values of the created objects. Even though our code appears to be easy to read, it has two major problems:
First, it doesn’t describe the meaning of the property values. This means that the person, who reads and writes this code, must be familiar with all states of a task. In other words, this person must be able to map different property values into a specific state and vice versa. This makes our test code hard to read and write.
Second, the logic that creates new Task objects is scattered all over the test classes which use Task objects. If we have to make changes to the existing states of the task, we have to make these changes to every test class that uses Task objects. This makes our tests hard to maintain because making these changes takes a lot of time. Also, this makes our tests more error prone because we might forget to make these changes. If we do so, we have a useless test suite because it doesn’t test the right thing anymore.
Let’s move on and find out when we shouldn’t use the JavaBeans style construction in our test classes.
When We Shouldn’t Use the JavaBeans Style Construction
We shouldn’t use the JavaBeans style construction in our test classes if any of the following conditions is true:
- The created object has different states. We shouldn’t use the JavaBeans style construction in this situation because our object creation logic (aka business rules) is scattered all over the test classes which use the created objects. This makes our tests hard to maintain.
- Our test suite has multiple test classes which require objects that have the same or slightly different property values. This is a bit tricky situation because duplicate code makes our tests hard to maintain. However, sometimes writing duplicate code is worth it because it might make our tests easier to read. In other words, if we are in this situation, we have compare the pros and cons of our options before we decide what to do.
Next, we will find out when we should use the JavaBeans style construction in our test classes.
When We Should Use the JavaBeans Style Construction
We should use the JavaBeans style construction in our test classes if any of the following conditions is true:
- The created object is used to transfer data. If the object is just a data container and it doesn’t have states, we should be happy to write data-centric code.
- The created objects are used by only one test class or a few test classes. Every decision is a trade-off and the other options have a cost. If we are in this situation, the cost of using other options is most likely higher than the benefits of using these options. That is why we should create our test data by using the JavaBeans style construction.
- We don’t care about the property values or the state of the created object. Sometimes we only want to ensure that a field of an object references to the correct object. If this the case, and we can create the referenced object by using a no-argument constructor, we should use the
newkeyword in our test class. The reason for this is that using other options such as a factory method or a test data builder is too “expensive”.
We can now identify the drawbacks of the JavaBeans style construction, and we know when we should use it in our test classes. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us four things:
- The JavaBeans style construction forces us to put our business rules to our test classes.
- We shouldn’t use the JavaBeans style construction in our test classes if the created object has different states.
- We should use the JavaBeans style construction in our test classes if the created object is just a data container.
- If the pros of the JavaBeans style construction outweigh its cons, we must use it in our test classes.