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).

Grails 3 Testing Hints/Tips

Grails 3.x changed a lot of things. Most for the good, but this introduced a number of changes in our test code.  Some of the misc hints/tips for testing are:
  • Don't ever put @Integration in a test super-class.  Only use this in the concrete test classes.
  • IDEA needs the tests to be marked with @Test to run in the IDEA/JUnit world.  Gradle can still run them, but the IDEA JUnit system misses them.
  • Don't use @Rollback together with .withTransaction{} in most cases.  The .withTransaction{} gets rolled back too.
  • Unit Tests need to use the save(flush:true)syntax now.
  • For GEB testing of GUIs, never use @Transaction.  Instead use .withTransaction{} to create records.  The test code will not commit until it exits the method, so the server will never see the data while the test method is running.
  • To test a controller in an integration test, you will need to send an HTTP request to the running server. I use HTTPClient for this.  A login is needed to do this (similar to GEB, but at the pure HTTP level, no GUI).
  • application.groovy is needed for custom GORM data types.  The application.yml does not support the grails.gorm.default.mapping.user-type syntax needed for a custom user type.
(The last one is not test related, but I did not want to create a new blog post for it :).

Some of these could be bugs that might need fixes, but I am just not sure they are worth the time to fix them.  The work-arounds seem acceptable.


Saturday, April 16, 2016

Big Gotcha with Adding New Artefact Type to Grails

I just recently lost a day fighting some issues with Grails and a custom artefact in my plugin development.  The cause was simple: some classes must be Java classes to work correctly.

I followed the format of the quartz plugin and created my own versions of the artefact handlers and such.  I missed that some of the files were .java files:

  • DefaultGrailsJobClass.java
  • GrailsJobClass.java

My clones of those two files were .groovy files.

Of course, I started getting weird results like stack over flow exceptions and methods not found (e.g. getName()).  Even getClass() would fail with a stack over flow.

It turns out Grails is using some metaClass magic with the artefact class DefaultGrailsJobClass, and you just can't use Groovy for them.  I causes the Grails magic to get stuck.

Also, you can store these .java files under the src/main/groovy without problems.

As usual, when you deviate from the Grails approach, odd things jump up and bite you...

Sunday, February 7, 2016

Running Grails 3 JUnit Tests 3-4 Times Faster with Intellij

Grails 3 uses Gradle for the build and most development tasks such as running unit tests.  This is great, but during the edit/compile/test cycle, it adds a good bit of overhead.  When you make a change and want to test it with a unit test, the Gradle process takes much longer than the Intellij/IDEA JUnit test runner (30sec vs. 7sec).

Why is this?  When you make a small change to your code under test, Gradle will recompile the entire groovy codebase.  This takes time.  Also, the Gradle process is much less efficient at detecting code changes than IDEA.  Gradle can take 25sec to compile the code while IDEA takes 3sec.

To make this work, you need to run the unit test using IDEA's JUnit test runner and use the built-in make logic:



Note: This uses the really nice action 'Run context configuration from editor' (Ctrl+Shift+F10 on Windows).  I have also set the JUnit default in the Run Configuration to set the VM Options to '-ea -Dserver.port=8091 -Dgrails.env=test'.  This is not needed for JUnit tests, but I use it for Integration and GUI tests (a future blog).

Also, the IDEA and Gradle compilers tend to step on each other's output class files.  This causes a lot of trouble when the classes are used together.  You need to set the test output path on the project settings to a directory that is different from the Gradle build directory ('.../testi' below):



This avoids the problem, but the output path is overwritten when IDEA updates the project settings from the build.gradle file.  So, if you get weird errors when testing, do a Gradle clean and then check the output settings above.

Update for IDEA 2016.1 (March 2016).  When upgrading, you will need to refresh the gradle project.  This changes the project structure in IDEA significantly, but it makes sure the JUnit tests are run correctly:


If you don't do this, then you get a lot of Spring DEBUG/INFO trace messages like this:

DEBUG o.s.w.c.s.StandardServletEnvironment - Initializing new StandardServletEnvironment

Welcome

Welcome.

This blog is meant to document some of my experiences in producing complex applications/plugins using Grails, Groovy and various tools.  This includes how to use Jetbrains Intellij/IDEA with Grails.

Grails is a very opinionated framework.  If you follow its conventions, then it is one of the most productive environments I know of for developing complex database-oriented web applications.  While Grails is very powerful and concise, if you stray outside of the lines, it can be difficult to diagnose problems.

This blog is meant to help others with these problems.  I considered just logging these on stackoverflow.com, but they don't like it when you post the question and answer together.

I hope others will benefit from my mistakes :).