Now you have all the essential knowledge needed in order to write your own Spock assertions. The rest of this chapter continues with the theme of reducing unit test code size with the expressiveness provided by Groovy compared to Java. All the follow- ing techniques are optional, and you can still use normal Java code in your Spock tests if your organization needs a more gradual change. Each application is different, so it’s hard to predict all the ways that Groovy can help you with unit tests. The following selection is my personal preference.
2.3.1 Using map-based constructors
If there’s one feature of Groovy that I adore, it’s object creation. Most unit tests create new classes either as test data or as services or helper classes used by the class under test. In a large Java application, a lot of statements are wasted creating such objects.
The next listing presents a Java example of testing a class that takes as its argument a list of persons.
Employee trainee = new Employee();
trainee.setAge(22);
trainee.setFirstName("Alice");
trainee.setLastName("Olson");
trainee.setInTraining(true);
Employee seasoned = new Employee();
seasoned.setAge(45);
seasoned.setFirstName("Alex");
seasoned.setMiddleName("Jones");
seasoned.setLastName("Corwin");
List<Employee> people = Arrays.asList(trainee,seasoned);
Department department = new Department();
department.assign(people);
[...rest of test]
Java needs more than 10 statements to create the two objects that will be used for test input. This boilerplate code is too noisy when compared with the code that tests the Department class.
Listing 2.11 JUnit test with multiple object creation statements
Java object that will be used as test input Filling of fields one by one
Second Java object for test input Filling of different
fields one by one
Class under test Test data is used.
47 Groovy features useful to Spock tests
EASY OBJECT CREATION WITH GROOVY CONSTRUCTORS
This is a well-known problem for Java developers. Sometimes special constructors are created for business domain objects to allow for easy testing. I consider this an antipat- tern. This technique not only shifts verbosity from unit tests to core code, but also has its own shortcomings in the case of multiple constructors. In the preceding example, the Employee class would be polluted with at least two constructors (one that sets the trainee flag and one that ignores it).
Groovy comes to the rescue! Map-based constructors are autogenerated for your Java objects, allowing your Spock tests to initialize any number of fields as well, as shown in the following listing.
when:
Employee trainee = new
Employee(age:22,firstName:"Alice",lastName:"Olson",inTraining:true) Employee seasoned = new Employee(middleName:"Jones",lastName:"Corwin",age:45,firstName:"Alex") List<Employee> people = Arrays.asList(trainee,seasoned)
Department department = new Department() department.assign(people)
[...rest of test]
Without changing a single line of Java code in the Employee class file, I’ve used the map-based constructors, whereby each field is identified by name and the respective value is appended after the semicolon character. Notice that the order of the fields and the set of the fields are completely arbitrary. With this technique,10 you can create a Java object with all possible combinations of its fields in any order that you like!
2.3.2 Using maps and lists in Groovy
The syntax shown in the previous section isn’t specific to constructors. This is the Groovy way of initializing a map. You can use it for creating a map in a single state- ment. The following listing presents an example.
Map<String,Integer> wordCounts = new HashMap<>();
wordCounts.put("Hello",1);
wordCounts.put("Java",1);
wordCounts.put("World",2);
Map<String,Integer> wordCounts2 = ["Hello":1,"Groovy":1,"World":2]
Listing 2.12 Spock test with map-based constructors
10 Groovy supports even-more-concise constructors. They sacrifice clarity, so I refrain from showing them here.
Listing 2.13 Groovy versus Java maps
Java object created with specific field values Another Java
object with different field values
Class under test Test data is used.
Manually filling a map (Java way) Groovy can
create and initialize a map.
You can create any kind of map like these, even those with keys and values that are classes on their own. For Groovy, it makes no difference (see the following listing).
Employee person1 = new
Employee(firstName:"Alice",lastName:"Olson",age:30) Employee person2 = new
Employee(firstName:"Jones",lastName:"Corwin",age:45)
Address address1 = new Address(street:"Marley",number:25) Address address2 = new Address(street:"Barnam",number:7)
Map<Employee,Address> staffAddresses = new HashMap<>();
staffAddresses.put(person1, address1);
staffAddresses.put(person2, address2);
Map<Employee,Address> staffAddresses2 = [(person1):address1,(person2):address2]
As shown in listing 2.13, when classes are used for the keys of the map, you need to use extra parentheses. If you don’t, the classes are assumed to be strings. Also, this concise Groovy syntax creates by default a LinkedHashMap.
In a similar way to maps, Groovy supports a concise syntax for lists. If you’ve ever wondered why it’s so easy to create an array in Java in a single statement but not a list, you’ll be happy to discover that Groovy has you covered. The following listing shows the comparison between Groovy and Java lists.
List<String> races = Arrays.asList("Drazi", "Minbari", "Humans") List<String> races2 = ["Drazi", "Minbari", "Humans"]
assert races == races2
String[] racesArray = ["Drazi", "Minbari", "Humans"]
String[] racesArrayJava = {"Drazi", "Minbari", "Humans"}
Because the syntax of arrays and lists is similar in Groovy, you might find yourself using arrays less and less as you gain experience with Groovy. Notice that the usual way of declaring arrays in Java is one of the few cases where valid Java is invalid Groovy. If you try to create an array in Groovy by using the Java notation, you’ll get an error, because Groovy uses the same syntax for closures, as you’ll see later in this chapter.
Listing 2.14 Groovy maps with nonscalar keys and values
Listing 2.15 Groovy versus Java lists
Creating a Java object by using map-based constructors
Filling a map manually (the Java way) Creating and initializing a map (the Groovy way)
Creating a list with data in Java Creating
a list with data in Groovy
The == operator tests equality in Groovy and not identity. This assert passes.
Creating an array with data in Groovy This is valid Java,
but invalid Groovy.
49 Groovy features useful to Spock tests
Using the knowledge you’ve gained from the preceding sections, you can com- pletely rewrite the JUnit test from listing 2.11, as the following listing shows.
List<Employee> people = [ new Employee(age:22,firstName:"Alice",lastName:"Olson", inTraining:true),
new Employee(middleName:"Jones",lastName:"Corwin",age:45, firstName:"Alex")
]
Department department = new Department() department.assign(people) [...rest of test]
By following Groovy conventions, I’ve replaced 11 Java statements with 1. The unit test is much more readable because it’s clearly split as far as test data creation and test data usage are concerned.
ACCESSING LISTS IN GROOVY BY USING ARRAY INDEX NOTATION
So far, I’ve demonstrated only how maps and lists are initialized. Let’s see how Groovy improves their usage as well, as shown in the next listing.
List<String> humanShips = ["Condor","Explorer"]
assert humanShips.get(0) == "Condor"
assert humanShips[0] == "Condor"
humanShips.add("Hyperion")
humanShips << "Nova" << "Olympus"
assert humanShips[3] == "Nova"
assert humanShips[4] == "Olympus"
humanShips[3] = "Omega"
assert humanShips[3] == "Omega"
Notice how writing and reading to a list uses the same syntax, and only the context defines the exact operation. Again, this syntax is optional, and you’re free to use the Java way of doing things even in Spock tests.
Groovy offers the same array-like syntax for maps as well, as shown in the following listing.
Map<String,String> personRoles = [:]
personRoles.put("Suzan Ivanova","Lt. Commander") personRoles["Stephen Franklin"]= "Doctor"
Listing 2.16 Creating Groovy lists and maps in test code
Listing 2.17 Using Groovy lists
Listing 2.18 Using Groovy maps
Groovy initialization of a list, using map-based constructor objects
Java class under test
Use of test data
Creating a list with two elements Java way
of getting
an element Groovy way of
accessing a list Java way of adding a new element Groovy way
of adding elements
Groovy way of replacing an element
Creating an empty map in Groovy Java way
of inserting
into map Groovy way of
inserting into map
assert personRoles.get("Suzan Ivanova") == "Lt. Commander"
assert personRoles["Stephen Franklin"] == "Doctor"
personRoles["Suzan Ivanova"]= "Commander"
assert personRoles["Suzan Ivanova"] == "Commander"
Lists and maps are one of the many areas where Groovy augments existing Java collec- tions. Groovy comes with its own GDK that sits on top of the existing JDK. You should spend some time exploring the GDK according to your own unit tests and discovering more ways to reduce your existing Java code.
So far, you’ve seen how Groovy enhances classes, fields, and collections. Let’s see how Groovy strings compare to Java strings.
2.3.3 Interpolating text with Groovy strings
I demonstrated Groovy strings (GStrings) at the beginning of this chapter by taking a single Java class and converting it to idiomatic Groovy in a gradual way. At the most basic level, Groovy strings allow for quick text templates of object properties, but they also can handle full expressions, as shown in the following listing.
SimpleDepartment sales = new SimpleDepartment(name:"Sales",location:"block C")
SimpleEmployee employee = new SimpleEmployee(fullName:"Andrew Collins",age:37,department:sales) System.out.println("Age is "+employee.getAge())
println "Age is $employee.age"
System.out.println("Department location
is at "+employee.getDepartment().getLocation())
println "Department location is at $employee.department.location"
println "Person is adult ${employee.age > 18}"
println "Amount in dollars is \$300"
println 'Person is adult ${employee.age > 18}'
When run, this code prints the following:
>groovy GroovyStrings.groovy Age is 37
Age is 37
Department location is at block C Department location is at block C Person is adult true
Amount in dollars is $300
Person is adult ${employee.age > 18}
Groovy string interpolation is certainly powerful, but for unit tests, their multiline capability is more interesting. Similar to other scripting languages, Groovy allows you to split a big string with newlines, as shown in the next listing.
Listing 2.19 Using Groovy strings
Java way of accessing map Groovy way
of accessing
map Groovy way of
replacing element
Creating Java objects with map-based constructors
Java way of accessing fields Groovy
way of accessing fields Using {}
for full expressions
Escaping the
$ character Disabling evaluation altogether with single quotes
51 Reading a test dataset from an external source
def "Another demo for Groovy multiline strings"() { when: "a paragraph is processed"
String input = '''I want you to know you were right. I didn't want \ to admit that. Just pride I guess. You get my age, you \ get kinda set in your ways. It had to be \ done. Don't blame yourself for what happened later.'''
WordDetector wordDetector = new WordDetector();
wordDetector.parseText(input);
then: "word count should be correct"
wordDetector.wordsFound() == 34 }
This is a great feature for unit tests that require text input of three to four lines that can be embedded directly on the source file. Multiline strings also support text inter- polation if you use double quotes instead of single, but inside unit tests, it’s clearer if they’re pure text (without text interpolation). For more lines of text, I also advise using a separate text file, as demonstrated in the next section.