4.1. Understanding Spock from the ground up

At the lowest level, a Spock test method is highly characterized by its individual blocks. This term is used for the code labels inside a test method. You’ve already seen the given-when-then blocks multiple times in the previous chapters, as shown in the following listing.

Listing 4.1. Spock blocks inside a test method

Apart from the given-when-then blocks, Spock offers several other blocks that express different test semantics. The full list is shown in table 4.1.

Table 4.1. Available Spock blocks

Spock block

Description

Expected usage

given: Creates initial conditions 85%
setup: An alternative name for given: 0% (I use given:)
when: Triggers the action that will be tested 99%
then: Examines results of test 99%
and: Cleaner expression of other blocks 60%
expect: Simpler version of then: 20%
where: Parameterized tests 40%
cleanup: Releases resources 5%

The last column shows the percentage of your unit tests that should contain each block. This number isn’t scientific, and is based only on my experience. Depending on your application, your numbers will be different, but you can get an overall indication of the importance of each block.

4.1.1. A simple test scenario

I hope you enjoyed the nuclear reactor example of the previous chapter. In this chapter, you’ll get down to earth with a more common[1] system that needs testing. The Java system you’ll test is an electronic shop that sells computer products via a website, which I guess is more familiar to you than the internals of a nuclear reactor. You can see an overview of the system in figure 4.1.

1

...and more boring. I know some of you were waiting for the software that tracks trajectories of nuclear missiles in order to launch countermeasures (as teased in chapter 1). Sorry to disappoint you.

Figure 4.1. Buying products in an electronic shop

I’ll show you most Spock blocks by testing the base scenario, in which a user adds one or more products in an electronic basket. The basket keeps the total weight (for shipping purposes) and the price of all products selected by the user. The class under test is that electronic basket. The collaborator class is the product, as shown in the following listing.

Listing 4.2. Java skeleton for an electronic basket

Notice that this code is used only for illustration purposes. A production-ready e-shop would be much different. Now let’s see all Spock blocks that you can use in your unit tests.

4.1.2. The given: block

You’ve already seen the given: block multiple times in previous chapters of the book. The given: block should contain all initialization code that’s needed to prepare your unit test. The following listing shows a unit test that deals with the weight of the basket after a product is selected by the user.

Listing 4.3. The given-when-then triad

The given: block sets up the scene for the test, as shown in figure 4.2. Its function is to get everything ready just before the method(s) that will be tested is/are called.

Figure 4.2. The given: block prepares a test.

Sometimes it’s tempting to place this initialization code in the when: block instead, and completely skip the given: block. Although you can have Spock tests without a given: block, I consider this a bad practice[2] because it makes the test less readable.

2

An exception to this rule is a simple test with just the expect: block. That’s why I have 85% in expected usage of the given: block.

Rewrite credit card billing example with a given: block

As a quick exercise, look at listing 1.8 in chapter 1 (the example with credit card billing) and rewrite it correctly, by properly constructing a given: block. Some examples in chapter 2 are also missing the given: block. Try to find them and think how you should write them correctly.

Unfortunately, in large enterprise projects, the code contained in the given: block can easily get out of hand. Complex tests require a lot of setup code, and often you’ll find yourself in front of a huge given: block that’s hard to read and understand. You’ll see some techniques for managing that initialization code in a more manageable manner later in this chapter and also in chapter 8.

4.1.3. The setup: block

The setup: block is an alias for the given: block. It functions in exactly the same way. The following listing contains the same unit test for the basket weight.

Listing 4.4. Using the setup alias

Using setup: or given: is a semantic choice and makes absolutely no difference to the underlying code or how the Spock test will run. Choosing between setup: and given: for the initialization code is a purely personal preference (see figure 4.3).

Figure 4.3. The given: and setup: blocks do exactly the same thing in Spock tests.

I tend to use the given: block, because I believe that the sentence flow is better (given-when-then). Also, the setup: block might be confusing with some of the life-cycle methods that you’ll see later in this chapter.

4.1.4. The when: block

The when: block is arguably the most important part of a Spock test. It contains the code that sets things in motion by triggering actions in your class under test or its collaborators (figure 4.4). Its code should be as short as possible, so that anybody can easily understand what’s being tested.

Figure 4.4. The when: block triggers the test and should be as simple as possible.

When I read an existing Spock test, I sometimes find myself focusing directly on the when: block, in order to understand the meaning of the test (bypassing completely the given: block).

The importance of the when: block

Every time you finish writing a Spock test, your first impulse should be to check the contents of the when: block. It should be as simple as possible. If you find that it contains too much code or triggers too many actions, consider refactoring its contents.

Put yourself in the shoes of the next developer who comes along and sees your Spock test. How long will it take to understand the actions performed by the when: block?

In listing 4.4, the when: block is a single statement, so it’s easy to understand what’s being tested. Even though the e-shop example is basic, the same concept should apply to your when: blocks. The contents should be one “action.” This action doesn’t have to be a single statement, but it must capture a single concept in your unit test. To explain this idea better, the following listing shows a bad use of a when: block.

Listing 4.5. A nontrivial when: block—don’t do this

The code comes from a Spock test I found in the wild.[3] How long does it take you to understand what this Spock test does? Is it easy to read the contents of the when: block?

3

Again I mean no disrespect to the author of the code. If you’re reading this, I thank you for providing a real Spock test available on the internet for my example.

What’s the class under test here? Notice also that all three blocks (setup-when-then) have no text description (another practice that I find controversial). This makes understanding the test even harder.

You’ll see some techniques for refactoring when: blocks later in this chapter. For now, keep in mind that the code inside the when: block should be short and sweet, as seen in the following listing.

Listing 4.6. Descriptive when: blocks

Even though the when: block is two statements here, they both express the same concept (adding a product to a basket). Understanding the when: block in this example is easy.

4.1.5. The then: block

The then: block is the last part of the given-when-then trinity. It contains one or more Groovy assertions (you’ve seen them in chapter 2) to verify the correct behavior of your class under test, as shown in figure 4.5.

Figure 4.5. The then: block verifies the behavior of the class under test.

Again, you’re not limited to a single statement, but all assertions should examine the same thing. If you have unrelated assertions that test different things, your Spock test should break up into smaller ones.

Note also that Spock has an automatic safeguard against Groovy asserts that aren’t really asserts (a common mistake). Assume that I wrote my Spock test like the following listing.

Listing 4.7. Invalid then: block

Running this test prints the following:

>mvn test
> BasketWeightSpec.groovy: 45: Expected a condition, but found an
assignment. Did you intend to write '==' ? @ line 45, column 3.
[ERROR] basket.currentWeight = (tv.weight + camera.weight)

This is a nice touch of Spock, and although it’s not bulletproof, it provides effective feedback when you start writing your first Spock tests.

4.1.6. The and: block

The and: block is a strange one indeed. It might seem like syntactic sugar at first sight because it has no meaning on its own and just extends other blocks, but it’s important as far as semantics are concerned. It allows you to split all other Spock blocks into distinctive parts, as shown in the next listing, making the code more understandable.

Listing 4.8. Using and: to split the given: block

Here you use the and: block to distinguish between the class under test (the Basket class) and the collaborators (the products), as illustrated in figure 4.6. In larger Spock tests, this is helpful because, as I said already, the initialization code can quickly grow in size in a large enterprise application.

Figure 4.6. The and: block allows you to include collaborator classes in a test.

It’s also possible to split the when: block, as shown in the following listing.

Listing 4.9. Using and: to split the when: block

This example might be trivial, but it also showcases the capability to have more than one and: block. It’s up to you to decide how many you need. In the case of the when: block, always keep in mind the rule outlined in the previous section: if your and: blocks that come after when: perform unrelated triggers, you need to simplify the when: block. Figure 4.7 demonstrates this scenario.

Figure 4.7. You can concatenate multiple and: blocks to a when: block.

The most controversial usage of the and: block occurs when it comes after a then: block, as shown in the next listing.

Listing 4.10. Using and: as an extension to a then: block

In this example, I use the and: block to additionally verify the number of products inside the basket, as illustrated in figure 4.8.

Figure 4.8. Using an and: block with a then: block is possible but controversial. You could be testing two unrelated things.

Whether this check is related to the weight of the basket is under discussion. Obviously, if the number of products inside the basket is wrong, its weight will be wrong as well; therefore, you could say that they should be tested together.

Another approach is to decide that the basket weight and number of products are two separate things that need their own respective tests, as shown in figure 4.9.

Figure 4.9. Instead of using an and: block with a then: block, consider writing two separate tests.

There’s no hard rule on what’s correct and what’s wrong here. It’s up to you to decide when to use an and: block after a then: block. Keep in mind the golden rule of unit tests: they should check one thing.[4] My advice is to avoid using and: blocks after then: blocks, unless you’re sure of the meaning of the Spock test. The and: blocks are easy to abuse if you’re not careful.

4

Alternatively, a unit test should fail for a single reason.

4.1.7. The expect: block

The expect: block is a jack-of-all-trades in Spock tests. It can be used in many semantic ways, and depending on the situation, it might improve or worsen the expressiveness of a Spock test.

At its most basic role, the expect: block combines the meaning of given-when-then. Like the then: block, it can contain assertions and will fail the Spock test if any of them don’t pass. It can be used for simple tests that need no initialization code (figure 4.10), and their trigger can be tested right away, as shown in the following listing.

Figure 4.10. An expect: block can replace given:, when:, and then: blocks.

Listing 4.11. Trivial tests with the expect: block

More preferably, the expect: block should replace only the when: and then: blocks, as shown in figure 4.11.

Figure 4.11. An expect: usually replaces a when: and a then: block.

This is my preferred use of the expect: block, as shown in the following listing.

Listing 4.12. expect: block replaces when: and then:

Because the expect: block accepts Groovy assertions, it can be used in other creative ways that distinguish it from the then: block that typically ends a Spock test. The following listing shows a given-expect-when-then test (as seen in the excellent presentation “Idiomatic Spock”[5] found at https://github.com/robfletcher/idiomatic-spock).

5

The presentation is by Robert Fletcher, but the specific example of expect: is by Luke Daley (co-author of the Spock framework and creator of Geb, which you’ll see in chapter 7).

Listing 4.13. Using expect: for preconditions

In this example, you use the expect: block to verify the initial state of the basket before adding any product. This way, the test fails faster if a problem with the basket occurs.

4.1.8. The cleanup: block

The cleanup: block should be seen as the “finally” code segment of a Spock test. The code it contains will always run at the end of the Spock test, regardless of the result (even if the test fails). The following listing shows an example of this.

Listing 4.14. Using cleanup: to release resources even if test fails

Assume for a moment that the implementation of the basket also keeps temporary files for the current contents for reliability purposes (or sends analytics to another class—you get the idea). The basket comes with a clearAllProducts() method that empties the basket and releases the resources (deletes temporary files) it holds. By placing this method in the cleanup: block, you ensure that this method always runs, even if the code stops at the then: block because of failure.

The cleanup: block concludes all possible Spock blocks. Continuing with the bottom-up approach, let’s see where these blocks go in your source code.

The where: block is shown in chapter 5

If you’ve been paying close attention, you must have noticed that I haven’t said anything about the where: block. The where: block is used exclusively for parameterized tests. There are so many things to discuss about parameterized Spock testing that it has its own chapter. Chapter 5 covers parameterized tests and the possible forms of the where: block, so keep reading to get the full picture on all Spock blocks.

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

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