Friday, May 6, 2016

Mocking DSL From a Plugin

Sometimes, you want to test some Grails logic that uses one of the nice DSLs (Domain Specific Language) from a plugin.  For example, you are using the Mail Service Plugin and want to test the code that calls it:

mailService.sendMail {
  to email
  subject title
  text body
}

This is not simple with normal mocking tools.  What you need is a simple Mock mechanism that records the method calls made via the DSL.  The DSLMock class (shown at the end) can handle this.  The test will look something like this:

@Testvoid testAlertLogging() {
  def mailDSLMock = DSLMock.mock(MailService, 'sendMail', [])
  service.mailService = new MailService()

  service.triggerAlert('dummy@abc.xyz')
  // check email was sent to the right user with the right subject/body
  assert mailDSLMock.result.contains([name: 'to', args: ['dummy@abc.xyz']])
  def subjectMap = mailDSLMock.result.find() { it.name == 'subject' }
  assert subjectMap.args[0] == 'Default Subject'

  def bodyMap = mailDSLMock.result.find() { it.name == 'text' }
  assert body.bodyMap.args[0].contains('alert')
}


The DSLMock will simply record the calls to the DSL and your test code must verify the correct calls were made.

The DSLMock class is:

class DSLMock {
  /**   
  * Creates a mock for the given clazz and method.   
  * @param clazz The class to add the mock to.   
  * @param method The method that starts the DSL.   
  * @param result The expected result of the DSL.   
  * @return The mock.   */
  static DSLMock mock(clazz, method, result) {
    def tester = new DSLMock()
    clazz.metaClass.static."${method}" = { Closure dsl ->
      dsl.delegate = tester
      dsl.resolveStrategy = Closure.DELEGATE_FIRST      dsl()
      return result
    }
    return tester
  }

  /**   * The list of method calls made to the mocked DSL and their arguments.   */

  def result = []

  /**   
  * Used to record the method calls made to the mock.   
  * @param name The method name.    
  * @param args The arguments.   
  * @return   */

  def methodMissing(String name, args) {
    result << [name: name, args: args.toList()]
  }
}

(Sorry for the formatting of the code.  I am still learning how to use the blog editor).

No comments:

Post a Comment