My grails Unit Test mocks out the MailService using an Expando and redefining the sendMail method. The original code uses a static constant called EMAIL_SUBJECT without a prefix. However, when I run the test and remove from Something.EMAIL_SUBJECT the "Something." it fails the assertion saying that mockMailService.subject was passed a null How can I improve the test so the "Something." is not needed on EMAIL_SUBJECT?
Snippet of Test Code:
def createMockMailService() {
def mockMailService = new Expando()
mockMailService.sendMail = { callable ->
callable.delegate = mockMailService
callable.resolveStrategy = Closure.DELEGATE_FIRST
callable.call()
}
mockMailService.subject = { header -> }
mockMailService
}
void testThis() {
def mockMailService = createMockMailService()
mockMailService.subject = { assert it == "value of constant" }
Something something = new Something()
something.mailService = mockMailService
something.doSomethingThatCallsMailService()
}
Code Under Test:
class Something {
static def EMAIL_SUBJECT = "value of constant"
def mailService = new SomeMailThing()
def doSomethingThatCallsMailService()开发者_StackOverflow社区 {
mailService.sendMail {
subject Something.EMAIL_SUBJECT // Remove Something prefix
}
}
}
That behaviour is strange on the first glance only. Root cause is the DELEGATE_FIRST
resolution strategy combined with delegate being an Expando instance in this case. DELEGATE_FIRST
looks for the EMAIL_SUBJECT
property in the delegate first, which is mockMailService
, which is an Expando. Expando doesn't throw groovy.lang.MissingPropertyException
on missing properties, instead it returns null. Thus the property is found on the delegate and not evaluated on the owner (which would be the Something
you want the property from).
You could change the resolution strategy to OWNER_FIRST
. If you want to stick with DELEGATE_FIRST
because the original mailService
calls the sendMail
closure parameter with this strategy, you can't use Expando for your mocked mail service. Instead you could use an ordinary object and do the metaprogramming on the metaClass.
It would look something like this:
def createMockMailService() {
def mockMailService = new Object()
mockMailService.metaClass.sendMail = { callable ->
callable.delegate = mockMailService
callable.resolveStrategy = Closure.DELEGATE_FIRST
callable.call()
}
mockMailService.metaClass.subject = { header -> }
mockMailService
}
精彩评论