3

I'm trying to validate that this groovy closure in a class called CUTService has the correct values:

mailService.sendMail {
    to 'hey@example.com'
    from 'hey@example.com'
    subject 'Stuff'
    body 'More stuff'
}

I've looked at https://github.com/craigatk/spock-mock-cheatsheet/raw/master/spock-mock-cheatsheet.pdf, but his syntax produces an error:

1 * mailService.sendMail({ Closure c -> c.to == 'hey@example.com'})

groovy.lang.MissingPropertyException: No such property: to for class: com...CUTService

I've looked at Is there any way to do mock argument capturing in Spock and tried this:

1 * mailService.sendMail({closure -> captured = closure })
assertEquals 'hey@example.com', captured.to

which produces:

groovy.lang.MissingPropertyException: No such property: to for class: com...CUTService

I also tried this:

1 * mailService.sendMail({captured instanceof Closure })
assertEquals 'hey@example.com', captured.to

which produces:

Too few invocations for:
1 * mailService.sendMail({captured instanceof Closure })   (0 invocations)

...

Unmatched invocations (ordered by similarity):
1 * mailService.sendMail(com...CUTService$_theMethod_closure5@21a4c83b)

What do I need to do to get this working?

Community
  • 1
  • 1
Don Branson
  • 13,631
  • 10
  • 59
  • 101

1 Answers1

6

When you write :

mailService.sendMail {
    to 'hey@example.com'
    from 'hey@example.com'
    subject 'Stuff'
    body 'More stuff'
}

In fact, you are executing the method sendMail, with a closure c. sendMail create a delegate, and call your closure with this delegate. Your closure is in reality executed as :

delegate.to('hey@example.com')
delegate.from('hey@example.com')
delegate.subject('Stuff')
delegate.body('More stuff')

To test this closure, you should create a mock, configure the delegate of your closure to this mock, invoke the closure, and verify the mock expectation.

As this task is not really trivial, it's better to reuse the mail plugin and create his own mail builder :

given:
  def messageBuilder = Mock(MailMessageBuilder)

when:
   // calling a service

then: 
    1 * sendMail(_) >> { Closure callable ->
      callable.delegate = messageBuilder
      callable.resolveStrategy = Closure.DELEGATE_FIRST
      callable.call(messageBuilder)
   }
   1 * messageBuilder.to("hey@example.com")
   1 * messageBuilder.from("hey@example.com")
Jérémie B
  • 10,611
  • 1
  • 26
  • 43
  • Did you mean `1 * mailService.sendMail`? There's no sendMail in the Spec. Nevertheless, with this approach, the test passes even when the CUT is wrong. – Don Branson Feb 19 '16 at 17:56
  • I can only show you the type of test you can write, and how it works, but I can't write a working test without more context, your classes, etc ! ;-) – Jérémie B Feb 19 '16 at 18:00
  • Or nearly so - had to use `1 * messageBuilder.to('hey@example.com')` instead of `1 * messageBuilder.to "hey@example.com"`. – Don Branson Feb 19 '16 at 18:04