Platinum Partner
java,framework

Yet Another Bit of Spock Love

I'm gradually converting a back-log of existing tests to Spock ... and some of them convert so beautifully, it hurts. Here's an example:

Before (Java and TestNG)

public class CronExpressionTest extends Assert
{
    /*
    * Test method for 'org.quartz.CronExpression.isSatisfiedBy(Date)'.
    */
    @Test
    public void testIsSatisfiedBy() throws Exception
    {
        CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005");

        Calendar cal = Calendar.getInstance();

        cal.set(2005, Calendar.JUNE, 1, 10, 15, 0);
        assertTrue(cronExpression.isSatisfiedBy(cal.getTime()));

        cal.set(Calendar.YEAR, 2006);
        assertFalse(cronExpression.isSatisfiedBy(cal.getTime()));

        cal = Calendar.getInstance();
        cal.set(2005, Calendar.JUNE, 1, 10, 16, 0);
        assertFalse(cronExpression.isSatisfiedBy(cal.getTime()));

        cal = Calendar.getInstance();
        cal.set(2005, Calendar.JUNE, 1, 10, 14, 0);
        assertFalse(cronExpression.isSatisfiedBy(cal.getTime()));
    }

    @Test
    public void testLastDayOffset() throws Exception
    {
        CronExpression cronExpression = new CronExpression("0 15 10 L-2 * ? 2010");

        Calendar cal = Calendar.getInstance();

        cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // last day - 2
        assertTrue(cronExpression.isSatisfiedBy(cal.getTime()));

        cal.set(2010, Calendar.OCTOBER, 28, 10, 15, 0);
        assertFalse(cronExpression.isSatisfiedBy(cal.getTime()));

        cronExpression = new CronExpression("0 15 10 L-5W * ? 2010");

        cal.set(2010, Calendar.OCTOBER, 26, 10, 15, 0); // last day - 5
        assertTrue(cronExpression.isSatisfiedBy(cal.getTime()));

        cronExpression = new CronExpression("0 15 10 L-1 * ? 2010");

        cal.set(2010, Calendar.OCTOBER, 30, 10, 15, 0); // last day - 1
        assertTrue(cronExpression.isSatisfiedBy(cal.getTime()));

        cronExpression = new CronExpression("0 15 10 L-1W * ? 2010");

        cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // nearest weekday to last day - 1 (29th is a friday in 2010)
        assertTrue(cronExpression.isSatisfiedBy(cal.getTime()));

    }
}

After (Spock)

@Unroll
class CronExpressionSpec extends Specification {

  def propertyMissing(String name) { Calendar[name] }

  def "isSatisfiedBy(#year, #month, #day, #hour, #minute, #second ) should be #satisfied for expression '#expr'"() {

    def cal = Calendar.getInstance();

    def exp = new CronExpression(expr)

    cal.set year, month, day, hour, minute, second

    expect:

    exp.isSatisfiedBy(cal.time) == satisfied

    where:
    expr                    | year | month   | day | hour | minute | second | satisfied
    "0 15 10 * * ? 2005"    | 2005 | JUNE    | 1   | 10   | 15     | 0      | true
    "0 15 10 * * ? 2005"    | 2006 | JUNE    | 1   | 10   | 15     | 0      | false
    "0 15 10 * * ? 2005"    | 2005 | JUNE    | 1   | 10   | 16     | 0      | false
    "0 15 10 * * ? 2005"    | 2005 | JUNE    | 1   | 10   | 14     | 0      | false
    "0 15 10 L-2 * ? 2010"  | 2010 | OCTOBER | 29  | 10   | 15     | 0      | true
    "0 15 10 L-2 * ? 2010"  | 2010 | OCTOBER | 28  | 10   | 15     | 0      | false
    "0 15 10 L-5W * ? 2010" | 2010 | OCTOBER | 26  | 10   | 15     | 0      | true
    "0 15 10 L-1 * ? 2010"  | 2010 | OCTOBER | 30  | 10   | 15     | 0      | true
    "0 15 10 L-1W * ? 2010" | 2010 | OCTOBER | 29  | 10   | 15     | 0      | true
  }
}

What a difference; the data-driven power of the where: block makes this stuff a bit of a snap, and you can see in once place, at a glance, what's going on. IDEA even lines up all the pipe characters automatically (wow!). It's obvious how the tests execute, and easy to see how to add new tests for new cases. By comparison, the TestNG version looks like a blob of code ... it takes a bit more scrutiny to see exactly what it is testing and how.

In addition, the propertyMissing() trick means that any property (including public static fields) of Calendar is accessible without qualification, making things look even nicer. This is what they mean by writing an "executable specification", rather than just writing code.

I can't say this enough: using any other framework for testing Java or Groovy code would simply not be logical.

 

Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}