Friday 14 September 2012

Using Spock to test Spring classes

As the previous post mentioned, Spock is a powerful DSL built on Groovy ideal for TDD and BDD testing and this post will describe how easy it is to use Spock to test Spring classes, in this case the CustomerService class from the post Using Spring Data to access MongoDB. It will also cover using Spock for mocking.

Spock relies heavily on the Spring's TestContext framework and does this via the @ContextConfiguration annotation. This allows the test specification class to load an application context from one or more locations.

This will then allow the test specification to access beans either via the annotation @Autowired or @Resource. The test below shows how an injected CusotmerService instance can be tested using Spock and the Spring TestContext: (This is a slightly contrived example as to properly unit test the CustomerService class as you would create a CustomerService class in the test as opposed to one created and injected by Spring.)

package com.city81.mongodb.springdata.dao

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration

import spock.lang.*

import com.city81.mongodb.springdata.entity.Account
import com.city81.mongodb.springdata.entity.Address
import com.city81.mongodb.springdata.entity.Customer

@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
class CustomerServiceTest extends Specification {

 @Autowired
 CustomerService customerService
 
 
 def setup() {
  customerService.dropCustomerCollection()
 } 
 
 def "insert customer"() {
  
  setup:
  
  // setup test class args
  Address address = new Address()
  address.setNumber("81")
  address.setStreet("Mongo Street")
  address.setTown("City")
  address.setPostcode("CT81 1DB")
 
  Account account = new Account()
  account.setAccountName("Personal Account")
  List<Account> accounts = new ArrayList<Account>()
  accounts.add(account)
  
  Customer customer = new Customer()
  customer.setAddress(address)
  customer.setName("Mr Bank Customer")
  customer.setAccounts(accounts)

  when:
  customerService.insertCustomer(customer)
  
  then:
  def customers = customerService.findAllCustomers()
  customers.size == 1
  customers.get(0).name == "Mr Bank Customer"
  customers.get(0).address.street == "Mongo Street"
  
 } 
}

The problem though with the above test is that MongoDB needs to be up and running so to remove this dependency we can Mock out the interaction the database. Spock's mocking framework provides many of the features you'd find in similar frameworks like Mockito.

The enhanced CustomerServiceTest mocks the CustomerRepository and sets the mocked object on the CustomerService.

package com.city81.mongodb.springdata.dao

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration

import spock.lang.*

import com.city81.mongodb.springdata.entity.Account
import com.city81.mongodb.springdata.entity.Address
import com.city81.mongodb.springdata.entity.Customer

@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
class CustomerServiceTest extends Specification {

 @Autowired
 CustomerService customerService
 
 CustomerRepository customerRepository = Mock()
 
 def setup() {
  customerService.customerRepository = customerRepository
  customerService.dropCustomerCollection()
 } 
 
 def "insert customer"() {
  
  setup:
    
  // setup test class args
  Address address = new Address()
  address.setNumber("81")
  address.setStreet("Mongo Street")
  address.setTown("City")
  address.setPostcode("CT81 1DB")
 
  Account account = new Account()
  account.setAccountName("Personal Account")
  List<Account> accounts = new ArrayList<Account>()
  accounts.add(account)
  
  Customer customer = new Customer()
  customer.setAddress(address)
  customer.setName("Mr Bank Customer")
  customer.setAccounts(accounts)
  
  when:
  customerService.insertCustomer(customer)
  
  then:
  1 * customerRepository.save(customer)
  
 } 
 
 def "find all customers"() {
 
  setup:
  
  // setup test class args
  Address address = new Address()
  address.setStreet("Mongo Street")
  
  Customer customer = new Customer()
  customer.setAddress(address)
  customer.setName("Mr Bank Customer")
  
  // setup mocking  
  def mockCustomers = []
  mockCustomers << customer
  customerRepository.findAll() >> mockCustomers
  
  when:
  def customers = customerService.findAllCustomers()
  
  then:
  customers.size() == 1  
  customers.get(0).name == "Mr Bank Customer"
  
 }
 
}


The CustomerRepository is by way of name and type although it could be inferred by just the name eg

def customerRepository = Mock(CustomerRepository)

The injected customerRepository is overwritten by the mocked instance and then in the test setup, functionality can be mocked.

In the then block of the insert customer feature, the number of interactions with the save method of customerRepository is tested and in the find all customers feature, the return list of customers from the findAll call is a mocked List,as opposed to one retrieved from the database.

More detail on Spock's mocking capabilities can be found on the project's home page.


Tuesday 11 September 2012

Testing Java using the Spock Framework

The testing framework Spock is a powerful DSL built on Groovy which enables easily writable and extremely readable tests which lends itself well to software development processes like TDD and BDD.

As an example of how much clarity Spock and Groovy bring to testing I'll compare it with the tests for the ForkJoinPool example class InterpolationService.

Every Spock test (or specification) extends spock.lang.Specification. This abstract class uses Spock's JUnit runner, org.spockframework.runtime.Sputnik, and contains useful methods for writing tests eg creating mock objects

There are four main aspects to a specification: Fields, Fixtures, Features and Helpers. There is no point going into depth on each as there are well documented on the Spock Basics wiki but we'll certainly concentrate on Features with respect to the comparison with the JUnit InterpolationService tests.

Features are instance methods which must contain at least one block where a block is one of six methods: setup, when, then, expect, cleanup and where. The blocks setup and cleanup are as you might expect but others are described in more detail below.

First though and the first few lines of the InterpolationServiceTest are shown below:

package com.city81.interpolation.service

import spock.lang.*

class InterpolateServiceTest extends Specification {

    @Shared def interpolateService = new InterpolateService()

The class under test ie InterpolationService is marked as being @Shared (ie all features use the same instance).  This annotation is ideal for expensive resources like a DB connection.

Next the first feature which tests the interpolation of two numbers with an even number of steps:

def "interpolate two numbers with even no. of steps"() {
            
            expect:
            interpolateService.interpolate(a, b, c) == d
            
            where:
            a   | b    | c | d
            5.0 | 25.0 | 4 | [5.0, 10.0, 15.0, 20.0, 25.0] 
            }


The equivalent JUnit assert test method would be:

@Test
 public void testInterpolateTwoValues() {
     List<Double> interpolatedList = 
         interpolateService.interpolate(5.0, 25.0, 4);

    assertEquals(5, interpolatedList.size());
    assertEquals(5.0, interpolatedList.get(0), 0);
    assertEquals(10.0, interpolatedList.get(1), 0);
    assertEquals(15.0, interpolatedList.get(2), 0);
    assertEquals(20.0, interpolatedList.get(3), 0);
    assertEquals(25.0, interpolatedList.get(4), 0);
}


The clarity in what you are testing is easy to see and it is so easy with Spock just to add another where line to test another scenario:

def "interpolate two numbers with even no. of steps"() {

            expect:
            interpolateService.interpolate(a, b, c) == d

            where:
            a   | b    | c | d
            5.0 | 25.0 | 4 | [5.0, 10.0, 15.0, 20.0, 25.0] 
            2.0 | 14.0 | 6 | [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0] 
            }

It is also worth noting at this point that Groovy uses BigDecimal for floating number numbers but if you want to denote doubles then numbers can be appended with a d eg 5.0d, -15.0d

Whilst the above demonstrates the expect and where blocks, the below shows how to test for exceptions using when and then blocks:

def "interpolate two numbers where first arg is null"() {
       
            when:
            interpolateService.interpolate(null, 25.0, 4)

            then:
            thrown(IllegalArgumentException)
            }

An alternative approach to using thrown(IllegalArgumentException) could be to bind a variable to access the exception:

then:
            IllegalArgumentException e = thrown()
            e.cause == InterpolateService.VALUE_ARG_NOT_NULL_EXCP_TEXT

You can also use the when and then blocks to order interactions although not if the first when throws an exception.

There is much more on Spock on it's Google code pages and the above only describes a few basics but the next post will look at Mocking and also examine it's use with Spring by testing code from a previous post. Might even get the Groovy code formatting working by then too!