Functional testing in Grails just got a bit sexier

Posted by: on Jan 8, 2009 | 24 Comments

Ok so here’s the 1.0 release in the spirit of “don’t worry be crappy” (can’t use that phrase enough these days) of a new functional testing plugin for Grails.

Actually its far from crappy already.

I have developed this partly as part of my work for my generous client Historic Futures Ltd. who agreed to open source this.

I plan to continue improving and maintaining it personally. It’s surely a bit rough around the edges but the benefits should be pretty obvious to those who use it.

So what are you waiting for? Run:

grails install-plugin functional-test

See the documentation

…but in a nutshell it is HTTP testing with HtmlUnit under the hood but the rest is pure groovy smarts, leveraging standard JUnit.

Example:

import functionaltestplugin.*
class TwitterTests extends FunctionalTestCase {
  void testSearch() {
    get('http://www.twitter.com')

    click "Search"

    assertStatus 200
    assertContentContains "search"

    form('searchForm') {
      q = "#grails"
      click "Search"
    }

    assertStatus 200
    assertContentContains "#grails"
  }
}

What’s special about this?

  1. Simplicity and “elasticity” by default = less fragile functional tests as a result
  2. Simple compact DSL/methods to learn – it tries to “do the right thing”. Let’s face it writing tests is really boring.
  3. Ability to get/post directly without browsing to a page first. eg REST testing
  4. No .properties configs

…and one more thing URL stacktrace. When a test fails the report includes a stack of the URLs at the point of failure. The xxx-out.txt file contains request parameters+headers, content received etc. This feature will be expanded in future to allow a proper request/response chain autopsy.

Caveats – things I definitely want to sort out pronto:

  • currently test output is a bit ropey, I would like to do much nicer stuff with custom output xml and rendering
  • no simple way to set post body
  • no simple way to parse out json/xml responses yet

All this and more will come, with your support! Contribs also welcome!

ENJOY!

24 Comments

  1. Pratik Patel
    January 8, 2009

    Marc, this looks pretty neat, looking forward to trying it out this weekend. How does this compare to canoo groovy-webtest?

  2. j pimmel
    January 8, 2009

    Looks very interesting! Would be interested to see more examples

    I assume this uses latest HtmlUnit which can handle Javascript very well now yeah?

  3. Scott Davis
    January 8, 2009

    Strong sauce! I love reading about all of the testing goodness that is coming out with Grails 1.1. Keep up the good work – the syntax here is really nice.

  4. Marc Palmer
    January 8, 2009

    Scott – thanks. There’s more work to do but I feel this is a great start, with remarkably little code required to wrap up HtmlUnit thanks to all the groovy magic.

    J Pimmel – more examples will come soon. Yes its the latest version of htmlunit at the time of development. Easy to update it too.

    Pratik – Well for me this is just more Grails-y than webtest.

    1. Your tests are in test/functional, your reports in test/reports

    2. It re-uses JUnit and familiar paradigms eg assertContentContains (vs. Webtest’s verifyText)

    3. The new plugin has strict and loose variants of assertions eg assertContentContains is case-INsensitive and ignores whitespace. This is what you want most of the time. assertContentContainsStrict does the check without whitespace/case stripping.

    4. The DSL is simpler and thus needs less knowledge and documentation. You don’t need to know what -kind- of field you’re setting. In this plugin you do x = “y”, in webtest you do setInputField/setCheckbox/setRadiobutton.

    5. You can trivially run the same tests against a different absolute url: grails functional-tests http://someprerelease-server

    6. Pure AJAX and REST testing should be less verbose and it is trivial to do a POST to a url with some params in the body.

  5. Peter Ledbrook
    January 13, 2009

    And the major bonus over WebTest: it’s not handicapped by relying on Ant tasks! That is the biggest problem with WebTest and the reason I have never liked it much. Just try mixing your WebTest code with some normal Groovy – weird stuff happens because the execution order isn’t what you expect.

    Great work Marc and I hope I can contribute in the future!

  6. Robert
    January 21, 2009

    Hi Marc,

    I’m looking forward to test your plugin. I tried to install it against Grails 1.0.4. But I can’t download it. Is it just working against Grails 1.1?

  7. Dennis Carroll
    January 26, 2009

    Although I have not yet used it, for me, installation of http://plugins.grails.org/grails-functional-test/tags/RELEASE_1_2_1/grails-functional-test-1.2.1.zip under Grails 1.0.4 appeared normal.

  8. mallu
    February 18, 2009

    Hi Marc,

    I am using functional test plugin, this is my code wht i wrote

    class AuthControllerFunctionalTests extends functionaltestplugin.FunctionalTestCase {

    void testLogin() {
    get(‘/auth’, ‘login’) {
    username “john”
    password “admin”
    }

    form(“loginForm”) {

    username = “john”
    password = “admin”
    click “submit”
    }

    assertContentContains “login”

    }
    }

    it is showing error when i run test case, i am using jsecurity plugin

    No signature of method: AuthControllerFunctionalTests.get() is applicable for argument types: (java.lang.String, java.lang.String, AuthControllerFunctionalTests$_testLogin_closure1) values: {“/auth”, “login”, AuthControllerFunctionalTests$_testLogin_closure1@1f21412}

    groovy.lang.MissingMethodException: No signature of method: AuthControllerFunctionalTests.get() is applicable for argument types: (java.lang.String, java.lang.String, AuthControllerFunctionalTests$_testLogin_closure1) values: {“/auth”, “login”, AuthControllerFunctionalTests$_testLogin_closure1@1f21412}
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at org.codehaus.groovy.runtime.MetaClassHelper.doConstructorInvoke(MetaClassHelper.java:535)
    at groovy.lang.MetaClassImpl.doConstructorInvoke(MetaClassImpl.java:2356)
    at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1255)
    at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1185)
    at groovy.lang.ExpandoMetaClass.invokeConstructor(ExpandoMetaClass.java:524)
    at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(InvokerHelper.java:809)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeNewN(ScriptBytecodeAdapter.java:230)
    at functionaltestplugin.FunctionalTestCase.invokeMethod(FunctionalTestCase.groovy:205)
    at

    please help to get out of problem.

    thanks
    mallu.

  9. Marc Palmer
    February 19, 2009

    Mallu – we have dealt with this on the grails user mailing list (the correct place for discussion really)… but to recap:

    You are calling get() wrong. It takes a single string URI and a closure. Not two strings.

    Also you appear to be duplicating your user + password assignments in the query and the form.

  10. Felipe
    March 6, 2009

    How about the WebRecorder for Canoo? Is there anything similar to it?

    Thanks,

    Felipe

  11. Todd
    April 5, 2009

    This looks like a really great start but the documentation is really half baked.
    For example how do I do a regular expression match?
    how do i get access to the result “out” object to look for something?
    How do I do a file upload?
    How do I do a assertContains on the resulting Javascript file?
    Where is the API documentation?
    Where is the link to the src?

    Overall This post and the plugin page on grails.org look like a great quick start, but then this project is so new that my junior developer is only able to do very basic testing.
    Appreciate the effort but this is far from “baked” or established as an opensource project.

  12. Marc Palmer
    April 14, 2009

    Todd – yes this is a new plugin, but its certainly not half baked.

    The docs could do with some improvement but they’re not too bad at the moment. Sometimes you may need to know a little HtmlUnit which is used underneath it all for more advanced work.

    The documentation is here http://grails.org/Grails+Functional+Testing
    The src is in standard plugin SVN repo https://svn.codehaus.org/grails-plugins/grails-functional-test/trunk

    To upload files, you access the file input field and set HTMLUnit properties as per the docs at http://htmlunit.sourceforge.net/apidocs/com/gargoylesoftware/htmlunit/html/HtmlFileInput.html

    eg:

    form(‘something’) {
    yourFileInputFieldName.contentType = “text/plain”
    yourFileInputFieldName.data = new File(‘something.txt’).bytes
    click “submit” // or whatever
    }

    Please raise jiras for any features you need.

  13. Marc Palmer
    April 14, 2009

    Felipe – no such recorder exists. It should be possible to adapt one like the Webtest one.

    Its not a high priority at the moment – the tests are so easy to write and expressive, that there’s not that much to be gained, given that your tests will need to be maintained/edited manually after recording them anyway.

  14. John
    April 14, 2009

    Marc,

    I have been using your plugin for about 2 weeks now and I have run into an issue as seen below…

    form(‘someForm’) {
    image.file = “photo.jpg” // image.file is a multipart file
    click “submit”
    }

    when compiling, I receive an error declaring that .file (of image.file) is a missing method. Have you any thoughts on the matter?

    I do highly appreciate you (and people like you) taking the time to make a better mousetrap to improve product development for people like me.

    Thanks in advance,

    John

  15. Gaurav
    April 18, 2009

    Hi Marc,

    I am using the impressive Functional Testing Plugin with my Grails App. I have this scenario and i am unable to find a solution for it. I have 2 buttons in my Form “APPROVE” and “DISAPPROVE” when the user click on ‘DISAPPROVE” an alert box is shown to user and when he click on OK then only we process the form If he click on “CANCEL” then nothing happens.

    As of now i am using the following Code:

    form(‘approvalForm’) {
    click ‘Disapproval’
    }
    click ‘Cancel’
    assertStatus 200

    But as it clicks on Disapproval the form get’s processed at it’s own and i get an error stating that ‘Cancel’ not found. I know that i am using javascript alert() to show that pop-up box so is there any way to simulate this behavior ?

    Any help will be appreciated.

    regards
    gaurav

  16. Greg Bridges
    April 20, 2009

    I tried to use BootStrap.groovy to set up some test data before the tests execute, but it appears to run the tests before BootStrap is executed. Is there a different way to accomplish this?

  17. Marc Palmer
    April 22, 2009

    Greg – functional tests run after bootstrap is run – G-Func builds and runs the entire app before running tests.

  18. Marc Palmer
    April 22, 2009

    Guarav it sounds like your onclick handling is not being called by the HtmlUnit browser, or there is some nuance with simulated alert dialogs. I will JIRA & look into it.

  19. Marc Palmer
    April 22, 2009

    John – that is not (yet) how file uploads are handled. The object you get when accessing a file upload field is a HtmlUnit HtmlFileInput instance. You need to set the contentType and data properties on it:

    http://htmlunit.sourceforge.net/apidocs/com/gargoylesoftware/htmlunit/html/HtmlFileInput.html

  20. Duncan Sommerville
    October 12, 2009

    It is possible to do ‘digest authentication’ with this plugin?

    I’ve hada look on HtmlUnit which talks about using ‘addCredentials()’, which sounds promising, – but how to I attach these properties when using this plugin?

  21. Duncan Sommerville
    October 12, 2009

    Managed to figure this out:

    class MyFunctionalTests extends FunctionalTestCase {
    void testMyThing() {
    client.getCredentialsProvider().addCredentials(“username”,”password”)


    }
    }

  22. Marc Palmer
    October 13, 2009

    Duncan thanks for that – I’ll add to g-func docs online.

  23. Javen
    February 21, 2010

    Hi Marc,

    I have two testCases in a Class, such as testCase1(),testCase2()
    How can I only run one?

  24. João Paulo
    June 16, 2011

    Hi!

    Just to help anyone that might come here looking for info on how to test file uploads, here is the code of the test that I got to work:

    void testRemoveReport() {
    FileWriter writer = new FileWriter(new File(“newReport.txt”));
    PrintWriter out = new PrintWriter(writer);
    out.println(“Blablabla”);
    out.close();
    writer.close();

    (here i get the page where i can upload a report)

    form() {
    report.contentType = “text/plain”
    report.data = new File(“newReport.txt”).bytes
    report.valueAttribute = new File(“newReport.txt”).absolutePath
    click “Submit”
    }

    assertContentContains “newReport.txt”
    }

    The only unmentioned thing here was the valueAttribute bit, where you have to set to the absolute path of your file.

    Hope I can help someone!