Spock, for tedious database conditions testing

As I mentioned earlier in my previous post, spock is a very nice groovy based testing framework, that helps us to write better readable test code. This is done through the use of groovy and by the use of behaviour driven development. In behaviour driven development you define what the system is supposed to do and express yourself in clear, hopefully, non programming terms. Groovy and spock helps you do just that. For instance, if I would like to test a valid login based on certain conditions, I could express that as follows:

def "An user should be allowed to login if his username and password are valid and the account has been verified"() {
}

Here it states immediately what this test does and what the conditions should be for a valid login. Inside this method we could then add the code to set up the user account and test if it is valid.

Let’s say, that we have an account class with a username and password and an indication whether the user has activated his account or not. Also for an account to be valid, there must be a connection with company. This company is a franchise and it has a start date and an end date. If today is after the end date, the franchise is ended and then the user is also not allowed to login. These relations are also stored in the database and we would like to test those relations.

So this means:

def "An user should be allowed to login for an active franchise"() {
  given: "an active franchise"
  Franchise franchise = testFixtures.getFranchise(companyId)
  franchise.setEndDate(new DateTime().plusMonths(1).toDate())
  and : "a validated user for this company"
  Account user = testFixtures.getAccount(companyId, username, password)
  user.setValidated(true)
  
  when: "this user logs in"
  Account account = accountService.login(companyId, username, password)
  
  then: "the account should proceed"
  account.proceed
}

This is (in my opinion) good readable, and that is what testing code should be all about, readability. Note that all the parameters are not shown here, they are defined as fields to the test class, its pseudo code. The testFixtures methods will try to get the object from the database and create one with the given values if it does not exist.

Now, what happens if we want to test if the user is not allowed to login due to the fact that he/she has not yet validated his/her account. Then the next test could be:

def "An user should not be allowed to login for an active franchise, if he is not validated yet"() {
  given: "an active franchise"
  Franchise franchise = testFixtures.getFranchise(companyId)
  franchise.setEndDate(new DateTime().plusMonths(1).toDate())
  and : "a not yet validated user for this company"
  Account user = testFixtures.getAccount(companyId, username, password)
  user.setValidated(false)
  
  when: "this user logs in"
  Account account = accountService.login(companyId, username, password)
  
  then: "the user's account should be access denied"
  ! account.proceed
}

This is still readable, but its a lot of code to test one sub condition. Imagine what the code would start to look like if we are going to test all of the conditions. Readable, but not without a good bang of your head on the table to stay awake. Luckily, spock can help out. It is possible to define parameters for a test that spock will use and run the test again and again using those parameters. Instead of ‘given’, ‘when’ and ‘then’ clauses, you can use ‘expect’ and ‘where’. In expect you define the test with the parameters, and in the where clause you define the content of the parameters. The spock site itself shows a simple example for this:

class HelloSpock extends spock.lang.Specification {
    def "length of Spock's and his friends' names"() {
        expect:
        name.size() == length

        where:
        name     | length
        "Spock"  | 5
        "Kirk"   | 4
        "Scotty" | 6
    }
}  

And using the same expect and where clause we can create one single test that will test all the conditions. Look and be amazed:

def "should test all the relations for a valid or non valid login"() {
  expect:
  testFixtures.getFranchise(companyId)
  franchise.setEndDate(date)
  Account user = testFixtures.getAccount(companyId, username, password)
  user.setValidated(validated)
  Account account = accountService.login(companyId, username, password)
  account.proceed == result

  where:
  companyId | date                        | validated | username | password | result
  1         | new DateTime().toDate()     | true      | "me"     | "pass"   | true
  2         | new DateTime().toDate()     | true      | "me"     | "pass"   | false
  1         | new DateTime().plusMonths(2)| true      | "me"     | "pass"   | false
  1         | new DateTime().toDate()     | false     | "me"     | "pass"   | false
  1         | new DateTime().toDate()     | true      | "you"    | "pass"   | false
  1         | new DateTime().toDate()     | true      | "me"     | "secret" | false
}

And now I have created 6 test cases, the first test case is the happy path, and all the other test cases change one condition to make the user not able to login anymore. This way it is readable and clear what those conditions are and you have all the conditions in one place. So hurray for spock