Wednesday, December 14, 2011

Advanced Spock Techniques

In recent years there have been a couple of tools that stand out when it comes to helping me be productive. One of those is the groovy test framework Spock. It is worthy of an introductory blog post... but that isn't this post. One of the challenges to Spock is the documentation isn't a complete as one would hope. The Spock Basics is fantastic when getting started but it is what it claims to be basics. I've been speaking with NFJS on the subject of Spock for the last half of 2011. On 2 occasions I've had the look our red shirted friend in the picture has when asked about controlling aspects of mocks in spock... meaning I didn't have the answer. In this case, google didn't discover it either... a quick email to the Peter Niederwieser reveals the previously undocumented solution. (at least that's my understanding)... and thanks Peter!

Here we will discuss 2 advanced aspects of mocking with spock. If you need more details on mocking in general hit the Spock site... or wait patiently for a future post:)

Problem Setup:

When mocking with Groovy we get 2 benefits from the verification of the mock. 1) order of execution, 2) failure if another method on the mock was executed but was not setup in the demand configuration.

void testDeposit() {

Account account = new Account(TEST_ACCOUNT_NO, 100)

def mock = new MockFor(AccountDao)
mock.demand.findAccount(TEST_ACCOUNT_NO) { account }
mock.demand.updateAccount(account) { assertEquals 150, account.balance }

def dao = mock.proxyDelegateInstance()
def service = new AccountServiceImpl(dao)

service.deposit TEST_ACCOUNT_NO, 50
mock.verify dao

Spock Cleanup

A similar solution in Spock might look like this:

def "deposit into account test refactor"() {

def mock = Mock(AccountDao)
def service = new AccountServiceImpl(mock)

service.deposit nmr, amt

mock.findAccount(_) >> account
1 * mock.updateAccount(_)
0 * mock.createAccount(_)

account.balance == total

nmr | account | amt | total
"101" | new Account("101", 100) | 50 | 150
"101" | new Account("101", 0) | 50 | 50

However this solution doesn't guarantee order nor do we want to use the 0 * mock. on all possible methods.

Spock Mock Solution

It turns out that we can repeatedly use the then: dsl to determine the order of events. So we can modify the Spock code above with the following changes.

1 * mock.findAccount("1234") >> account

_ * mock.updateAccount(_)

In this case we are saying that the findAccount() method will be called exactly once and return the account object and "then" the updateAccount will be call any number of times with any type of argument. The test will fail if the updateAccount is ever called prior to the findAccount being invoked first.

We can use this same technique to solve the strictness concern we have as well by adding one more then: block of code as outlined below:

1 * mock.findAccount("1234") >> account

_ * mock.updateAccount(_)

0 * mock._

In this case the last 0 * mock._ says to expect nothing else. Ahhh.. the beauties of a dynamic language:)

Happy Coding... may your builds never fail and your tests always green bar!

No comments: