Testing with Spock

Spock is a testing framework based on the Groovy language but can also be used for regular Java projects. It is based on JUnit so running the test cases is as easy as running any regular JUnit tests, be it from an IDE or continuous integration servers. The framework aims to allow developers and testers to write specifications that are easily read and, ideally, may serve as documentation for actual code.

Spock is specially interesting in testing regular Java applications as it uses well many dynamic functionalities provided by Groovy, offering a nice set of options to write meaningful specifications. The specifications output is also nicely formatted to show what are the causes of the failure, as in:

Spock Failure output
Condition not satisfied:

    max(a, b) == c
    |   |  |  |  |
    3   1  3  |  2
              false

As in JUnit a Test Case is composed by a set of tests, in Spock a Specification is composed by a set of feature methods.
Feature methods are sub-divided in logical block, each of them containing a label, meaning and a set of statements.
The available blocks are:

given (or setup);
when
then
expect
where

Additionally, the block and can be used to connect statements inside a block without changing the test behaviour but improving the meaning and readability.

The basic block usage are:

  • [given: <initialization>] when: <stimulus> then: <condition>
  • [given: <initialization>] expect: <condition>
  • [given: <initialization>] expect: <condition> where: <parameterization>

Spock Specifications use assertions implicitly, so the logical conditions must be placed in the logical blocks where they belong to.
For comparison, here are some test cases written in Java with JUnit and one equivalent Spock feature method for each of them:

Asserts project tariff is used by activity price calculation - unit test
  @Test
  public void testGetPriceProjectUsed() {
    project.setTariff(10.0);
    assertEquals(25f, activity.getActivityPrice(), 0f);

    project.setTariff(20.0);
    assertEquals(50f, activity.getActivityPrice(), 0f);
  }
Spock feature method
  def "project's price is used to calculate activity's price"() {
    when: 
      project.tariff = 10.0
    then:
      activity.activityPrice == 25.0
    
    when:
      project.tariff = 20.0
    then:
      activity.activityPrice == 50.0
  }

where is a special block to parameterize a feature method.
In this block local variables used in the feature method have their values re-assigned so that the same method can be used to test the same feature in different situations. One single feature method runs as many times as the variable is re-assigned.
The assignments are done using lists literals or external/helper methods that return collections.

The following example tests the same thing than the previous test, but it is written using expect/where.
In this case, the feature method runs twice, each time with different values.
Check how the variables are actually defined inside the where block and used before it.

Spock feature method
  def "project's price is used to calculate activity's price - another way to test"() {
    given:
      project.tariff = newTariff
    expect:
      activity.activityPrice == expectedPrice
      
    where:
      newTariff     << [10.0, 20.0]
      expectedPrice << [25.0, 50.0]
  }  

Another simple example:

Asserts an activity's price is not changed after it is closed
  @Test
  public void activityPriceDoesntChangeAfterClosing() {
    project.setTariff(10.0);
    assertEquals(25f, activity.getActivityPrice(), 0f);
    
    activity.setStatus(ActivityStatus.CLOSED);
    project.setTariff(20.0);
    assertEquals(25f, activity.getActivityPrice(), 0f);
  }
Spock feature method
  def "activity's price doesn't change after it is closed"() {
    given:
      project.tariff = 10.0
    expect:
      activity.activityPrice == 25.0
    
    when:
      activity.status = CLOSED
      project.tariff = 20.0      
    then:
      activity.activityPrice == 25f
  }

Spock is also very useful when there's the need to use mocks, or interactions:

Project owner can't delete projects without activities but with expenses reports
  @Test
  public void projectOwnerCanNotDeleteEmptyProjectWithExpenses() throws Exception {
    expect(userService.getCurrentUser()).andReturn(user);
    expect(permissionDao.getProjectUserRole(user.getId(), project.getId())).andReturn(
        Role.PROJECT_OWNER);
    expect(projectDao.hasActivities(project)).andReturn(false);
    expect(expenseReportDao.hasExpenseItems(project)).andReturn(true);

    replay(userService, permissionDao, projectDao, expenseReportDao);

    try {
      projectService.deleteProject(project);
      fail();
    } catch (BindException e) {
      verify(userService, permissionDao, projectDao, expenseReportDao);
    }
  }
Spock feature method
  def "project can't be deleted if it has expenses even if it has no activities"() {
    given:
      1 * userService.getCurrentUser() >> user
      1 * permissionDao.getProjectUserRole(user.id, project.id) >> Role.PROJECT_OWNER  
      1 * projectDao.hasActivities( { it.id == project.id } ) >> false
      1 * expenseReportDao.hasExpenseItems(project) >> true
      
    when:
      service.deleteProject(project)
    
    then:
      0 * projectDao.delete(project)
      thrown(BindException)
  }

The method can even be rewritten using helper methods (that can be re-used by other feature methods) so that it is even more readable:

Refactored Spock feature method
  def "project can't be deleted if it has expenses"() {
    given:
      currentUserIsProjectOwner()
      projectHasExpenses()
      projectHasNoActivities()
      
    when:
      service.deleteProject(project)
    
    then:
      projectIsNotDeleted()
      thrown(BindException)
  }

  private void currentUserIsProjectOwner() {
    1 * userService.getCurrentUser() >> user
    1 * permissionDao.getProjectUserRole(user.id, project.id) >> Role.PROJECT_OWNER  
  }
  
  private void projectHasExpenses() {
    1 * expenseReportDao.hasExpenseItems(project) >> true
  }

  private void projectHasNoActivities() {
    1 * projectDao.hasActivities( { it.id == project.id } ) >> false
  }

  private void projectIsNotDeleted() {
    0 * projectDao.delete(project)
  }

To use Spock in a Java project with Eclipse one has to install Groovy Eclipse Plug-in (available at http://groovy.codehaus.org/Eclipse+Plugin), add the Groovy nature to the project (which will import Groovy libs), and finally add Spock and JUnit 4.7 jar files to the build path.
That done, new Spock Specifications can already be added to the project and run from inside Eclipse as JUnit tests.

Conclusions

Spock seems to be a very interesting option for writing meaningful tests. Even though at first the division of feature methods in logical blocks might be a bit tricky for programmers who are used to just enumerate a sorted set of statements and assertions to write regular unit tests. But it shouldn't be a problem as it's very easy to understand the blocks concept and what each one should contain.
The resulting specifications look definitely more interesting than regular unit tests. This added to the ease to start using the framework make Spock a tempting alternative for writing tests if compared to the old boring styles.

But, in the other hand, bad unreadable tests can be written even using Spock Specifications. The same way, a good developer can write meaningful and readable tests even using plain JUnit.
In the end it all comes down to the ones operating the keyboard which doesn't diminish Spock's very good job at what it intends.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jan 20

    Ürgo Ringo says:

    Very nice post! It would be interesting to know how Spock compares with easyb.

    Very nice post!

    It would be interesting to know how Spock compares with easyb.

    1. Feb 23

      Luiz Ribeiro says:

      I didn't really look into easyb but I did take a quick look at it and from what ...

      I didn't really look into easyb but I did take a quick look at it and from what I saw it is very very similar.
      I like more the syntax of Spock though. :)

  2. Feb 23

    Timur Strekalov says:

    Live long and prosper

    Live long and prosper

Add Comment