When you create a unit test to examine the behavior of a specific class, you’ll find yourself writing the same code over and over again in the given: block. This makes sense because several test methods have the same initial state and only a different trigger (the when: block). Instead of copying and pasting this behavior (and thus violating the DRY[6] principle), Spock offers you several facilities to extract common preconditions and post-conditions of tests in their own methods.
This acronym stands for don’t repeat yourself. See https://en.wikipedia.org/wiki/Don’t_repeat_yourself for more information.
In the Spock test that examines your imaginary electronic basket, I’ve duplicated the code that creates products multiple times. This code can be extracted as shown in the following listing.
Spock will detect a special method called setup() and will run it automatically before each test method. In a similar manner, Spock offers a cleanup() method that will run after each test method finishes. A full example is shown in the following listing.
As with the cleanup: block, the cleanup() method will always run, regardless of the result of the test. The cleanup() method will even run if an exception is thrown in a test method.
The code you place inside the setup() and cleanup() methods will run once for each test method. If, for example, your Spock test contains seven test methods, the setup/cleanup code will run seven times as well. This is a good thing because it makes each test method independent. You can run only a subset of test methods, knowing they’ll be correctly initialized and cleaned afterward.
But sometimes you want initialization code to run only once before all test methods. This is the usual case when you have expensive objects that will slow down the test if they run multiple times. A typical case is a database connection that you use for integration tests, but any long-lived expensive object is a good candidate for running only once.
Spock supports this case as well, as shown in the following listing.
If you run this unit test, it will print the following:
Will run only once Will run before EACH feature first feature runs Will run once after EACH feature Will run before EACH feature second feature runs Will run once after EACH feature Will run once at the end
If you’re familiar with JUnit, you’ll notice that the Spock lifecycle methods work exactly like the annotations @Before, @After, @BeforeClass, and @AfterClass. Spock honors these annotations as well, if for some reason you want to continue to use them.
Because setupSpec() and cleanupSpec() are destined to hold only long-lived objects that span all the test methods, Spock allows code in these methods to access only static fields (not recommended) and objects marked as @Shared, as you’ll see in the next section.
You can indicate to Spock which objects you want to survive across all test methods by using the @Shared annotation. As an example, assume that you augment your electronic basket with a credit card processor:
public class CreditCardProcessor { public void newDayStarted() { [...code redacted for brevity..] } public void charge(int price) { [...code redacted for brevity..] } public int getCurrentRevenue() { [...code redacted for brevity..] } public void shutDown() { [...code redacted for brevity..] } }
CreditCardProcessor is an expensive object. It connects to a bank back end and allows your basket to charge credit cards. Even though the bank has provided dummy credit card numbers for testing purposes, the initialization of the connection is slow. It would be unrealistic to have each test method connect to the bank again. The following listing shows the solution to this problem.
Here you mark the expensive credit card processor with the @Shared annotation. This ensures that Spock creates it only once. On the other hand, the electronic basket itself is lightweight, and therefore it’s created multiple times (once for each test method). Notice also that the credit card processor is closed down once at the end of the test.
The old() method of Spock is a cool trick, but I’ve yet to find a real example that makes it worthwhile. I mention it here for completeness, and because if you don’t know how it works, you might think it breaks the Spock lifecycle principles. You use it when you want your test to capture the difference from the previous state instead of the absolute value, as shown in the following listing.
Here you have a unit test that checks the weight of the basket after a second product is added. You could check for absolute values in the then: block (assert that the basket weight is the sum of two products), but instead you use the old() method and say to Spock, “I expect the weight to be the same as before the when: block, plus the weight of the camera.” Figure 4.13 illustrates this.
The difference in expression is subtle, and if you find the old() method confusing, there’s no need to use it at all.
3.12.153.212