8.2. Handling large Spock tests

The projects in most examples so far are trivial projects designed as a learning material instead of production-quality applications. In the real world, enterprise projects come with huge code bases that directly affect the size of unit tests.

Even in the case of pure unit tests (non-integration tests), preparing the class under test and its collaborators is often a lengthy process with many statements and boilerplate code that’s essential for the correct functionality of the Java code tested, but otherwise unrelated to the business feature being tested.

I’ve provided some hints for making clear the intention of Spock tests using Groovy with() and Spock with() methods, as seen in chapter 4. In this section, you’ll take this grouping of statements one step further by completely refactoring the respective statements in their own methods.

The running example here is a loan-approval application, shown in figure 8.4.

Figure 8.4. A customer requests a loan from a bank. The bank approves or rejects the loan.

The Java classes that take part in the system are as follows:

  • Customer.java
  • Loan.java
  • CreditCard.java
  • ContactDetails.java
  • BankAccount.java

You can find the full source code in the GitHub repository of the book,[5] but notice that most classes are only skeletons designed to demonstrate specific techniques in the Spock tests.

5

8.2.1. Using helper methods to improve code readability

Chapter 4 stressed the importance of the when: block and how critical it is to keep its code short and understandable. But in big enterprise projects, long code segments can appear in any Spock block, harming the readability of the test. As a starting example, let’s see a unit test that has a long setup process, shown in the next listing.

Listing 8.15. A Spock test with long setup—don’t do this

At first glance, this unit test correctly follows the best practices outlined in chapter 4. All the blocks have human-readable descriptions, the when: block clearly shows what’s being tested (a loan request), and the final result is also clear (either the loan is approved or it’s rejected).

The setup of the test, however, is a gigantic piece of code that’s neither clear nor directly relevant to the business case tested. The description of the block talks about credit cards but contains code that creates both credit cards and bank accounts (because apparently a credit card requires a valid bank account in place).

Even with the use of the with() method for grouping several statements that act on the same project, the setup code makes the test hard to read. It contains a lot of variables, and it’s not immediately clear whether they affect the test. For example, does it matter that the account balance is $30 in each connected account? Does this affect the approval of the loan? You can’t answer that question by reading the Spock test.

In such cases, a refactoring must take place so that the intention of the test becomes clear and concise. Large amounts of code should be extracted to helper methods, as shown in the next listing.

Listing 8.16. Spock test with helper methods

Here you extract the common code into a helper method. The helper method has the following positive effects:

  • It reduces the amount of setup code.
  • It clearly shows that the setup code is a set of sample credit cards.
  • It hides the fact that a bank account is needed for creating a credit card (as this is unrelated to the approval of a loan).
  • It shows by its arguments that the holder of the credit card must be the same as the customer who requests the loan.

The added advantage of helper methods is that you can share them across test methods or even across specifications (by creating an inheritance among Spock tests, for example). You should therefore design them so they can be reused by multiple tests.

Depending on your business case, you can further refine the helper methods you use to guide the reader of the test to what exactly is being tested. In a real-world project, you might modify the Spock test as shown in the following listing.

Listing 8.17. Using arguments that imply their importance in the test

This improved listing makes minor adjustments to the arguments of the helper method. First, you use a single variable for the customer name. This guards against any spelling mistakes so you can be sure that all credit cards are assigned to the same customer (because as the description of the test says, the number of credit cards of the customer is indeed examined for loan approval).

Second, you replace the credit card numbers with dummy strings. This helps the reader of the test understand that the number of each credit card isn’t used in loan approval.

As a final test, you add an expect: block (as demonstrated in chapter 4) that strengthens the readability of the setup code.

After all these changes, you can compare listings 8.15 with 8.17. In the first case, you have a huge amount of setup code that’s hard to read, whereas in the second case, you can understand in seconds that the whole point of the setup code is to assign credit cards to the customer.

8.2.2. Reusing assertions in the then: block

Helper methods should be used in all Spock blocks when you feel that the size of the code gets out of hand. But because of technical limitations, the creation of helper methods for the then: block requires special handling.

Again, as a starting example of a questionable design, let’s start with a big then: block, as shown in the next listing.

Listing 8.18. Spock test with dubious then: block

Here the then: block contains multiple statements with different significance. First, you have some important checks that confirm that the loan is indeed approved. Then you have other checks that examine the details of the approved loan (and especially the fact that they match the customer who requests it). Finally, it’s not clear whether the numbers and strings that take part in the then: block are arbitrary or depend on something else.[6]

6

In this simple example, it’s obvious that the contact details of the loan are the same as the customer ones. In a real-world unit test, this isn’t usually the case.

As a first step to improve this test, you’ll split the then: block into two parts and group similar statements, as shown in the following listing.

Listing 8.19. Improved Spock test with clear separation of checks

The improved version of the test clearly splits the checks according to the business case. You’ve replaced the number 60, which was previously a magic number, with the full logic that installments are years times 12 (for monthly installments).

The code that checks loan details still has hardcoded values. You can further improve the code by using helper methods, as shown in the next listing.

Listing 8.20. Using helper methods for assertions

This listing refactors the two separate blocks into their own helper methods. The important thing to note is the format of each helper method.

Your first impulse might be to design each helper method to return a Boolean if all its assertions pass, and have Spock check the result of that single Boolean. This doesn’t work as expected.

The recommended approach, as shown in listing 8.20, is to have helper methods as void methods. Inside each helper method, you can put one of the following:

  • A group of assertions with the Spock with() method
  • A Groovy assert but with the assert keyword prepended

Notice this line:

assert customer.activeLoans == 1

Because this statement exists in a helper method and not directly in a then: block, it needs the assert keyword so Spock can understand that it’s an assertion. If you miss the assert keyword, the statement will pass the test regardless of the result (which is a bad thing).

This listing also refactors the second helper method to validate loan details against its arguments instead of hardcoded values. This makes the helper method reusable in other test methods where the customer could have other values.

Spend some time comparing listing 8.20 with the starting example of listing 8.18 to see the gradual improvement in the clarity of the unit test.

8.2.3. Reusing interactions in the then: block

As you saw in the previous section, Spock needs some help to understand assertions in helper methods. A similar case happens with mocks and interactions.

The following listing shows an alternative Spock test, in which the loan class is mocked instead of using the real class.[7]

7

In this example, mocking the loan class is overkill. I mock it for illustration purposes only to show you helper methods with mocks.

Listing 8.21. Spock tests with questionable then: block

The test in this listing contains multiple interaction checks in the then: block that have a different business purpose. The Loan class is used in this case both as a mock and as a stub. This fact is implied by the cardinalities in the interaction checks.

You can improve this test by making clear the business need behind each interaction check, as seen in the next listing.

Listing 8.22. Explicity declaring helper methods with interactions

You’ve created two helper methods and added a then: block. The first helper method holds the primary checks (the approval of the loan with its original values). The other helper method is secondary, as it contains the stubbed methods of the loan object (which are essential for the test but not as important as the approval/rejection status of the loan).

The important thing to understand in this listing is that you wrap each helper method in an interaction block:

interaction {
           loanDetailsWereExamined(loan)
    }

This is needed so that Spock understands the special format of the N * class.method (N) interaction check, as shown in chapter 6. Spock automatically understands this format in statements found directly under the then: block, but for helper methods you need to explicitly tell Spock that statements inside the method are interaction checks.

Constructing a custom DSL for your testing needs

The Groovy language is perfect for creating your own domain-specific language (DSL) that matches your business requirements. Rather than using simple helper methods, you can take your Spock tests to the next level by creating a DSL that matches your business vocabulary. Creating a DSL with Groovy is outside the scope of this book, so feel free to consult chapter 19 of Groovy in Action, Second Edition, by Dierk Koenig et al. (Manning Publications, 2015) for more information on this topic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.249.198