4.3. Exploring the lifecycle of a Spock test

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.

6

This acronym stands for don’t repeat yourself. See https://en.wikipedia.org/wiki/Don’t_repeat_yourself for more information.

4.3.1. Setup and cleanup of a feature

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.

Listing 4.19. Extracting common initialization code

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.

Listing 4.20. Extracting common pre/post conditions

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.

4.3.2. Setup and cleanup of a specification

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.

Listing 4.21. All Spock lifecycle methods

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
Compatibility with JUnit lifecycle methods

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.

4.3.3. Long-lived objects with the @Shared annotation

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.

Listing 4.22. Using the @Shared annotation

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.

4.3.4. Use of the old() method

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.

Listing 4.23. Asserting with the old() method

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.

Figure 4.13. The old() method allows you to access values set before the when: block in a test.

The difference in expression is subtle, and if you find the old() method confusing, there’s no need to use it at all.

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

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