Home > grails, groovy, howto > Unit Testing Delete Actions in Grails

Unit Testing Delete Actions in Grails

September 4th, 2009 Flo Leave a comment Go to comments

I’m quite new in Groovy and Grails and wanted to share some of my lessons learned while
writing my first unit tests in Grails.

Fortunately at the time I started unit testing in Grails the Testing Plugin had already moved into Grails core.
The documentation is quite good so it’s a good idea to start there.

Today I had a little problem testing the delete method on a Controller. Let’s say I have a Domain class Song which looks like

//  Song.groovy
class Song {
    String name =""
}

I have a method in my Controller that deletes a Song like the delete action from static scaffolding. After calling the action I want to make sure that my count of Songs has decreased.
My first attempt looked like:

// SongTests.groovy
class SongTests extends GrailsUnitTestCase {

    def testSongs
    Song s1, s2

    protected void setUp() {
        super.setUp()
        s1 = new Song(name: "Song 1")
        s2 = new Song(name: "Song 2")
        testSongs = [s1, s2]
        mockDomain(Song, testSongs)
    }

    void testDelete() {
        int oldSize = testSongs.size()
        s1.delete() // Note: Normally this would happen somewhere in the controller action.
        assertEquals oldSize - 1, testSongs.size()
    }
}

But this test struggles to pass. I did some println’s and looked at the testSongs List and noticed that it did not change. After doing some research I found the cause of my problem in MockUtils.groovy

//  MockUtils.groovy
....
    static GrailsDomainClass mockDomain(Class clazz, Map errorsMap, List testInstances = []) {

        ....
        def rootInstances = testInstances.findAll { clazz.isInstance(it) } // findAll creates a new List!!!
        def childInstances = testInstances.findAll { clazz.isInstance(it) && it.class != clazz }.groupBy { it.class }

        TEST_INSTANCES[clazz] = rootInstances
        addDynamicFinders(clazz, rootInstances)
        addGetMethods(clazz, dc, rootInstances)
        addCountMethods(clazz, dc, rootInstances)
        addListMethod(clazz, rootInstances)
        addValidateMethod(clazz, dc, errorsMap, rootInstances)
        addDynamicInstanceMethods(clazz, rootInstances)
        addOtherStaticMethods(clazz, rootInstances)
...
}

private static void addDynamicInstanceMethods(Class clazz, List testInstances) {
...
        // Add delete() method.
        clazz.metaClass.delete = { Map args = [:] ->
            for (int i in 0..<testInstances.size()) {
                if (testInstances[i] == delegate) {
                    testInstances.remove(i)
                    break;
                }
            }
        }
...
}

The problem was, that findAll creates a new list. Since delete just removes the element from the internal list, the original list keeps passed in as second parameter to the mockFor method stays
untouched.

With this in mind the updated test looked like:

    void testDelete() {
        int oldSize = Song.count()
        s1.delete()
        assertEquals oldSize - 1, Song.count()
    }

Which works perfectly.
If you are just getting started too I would recommend you the following sites which give a good introduction to this topic:

The Definitive Guide to Grails Book from Graeme Rocher and Jeff Brown:
http://www.amazon.de/Definitive-Guide-Grails-Experts-Voice/dp/1590597583

Glen Smith’s Blog – MockFor(March) Series:
http://blogs.bytecode.com.au/glen/2008/03/04/mockfor-march—overcoming-grails-testing-inertia.html
http://blogs.bytecode.com.au/glen/2008/03/07/mockfor-march—unit-testing-grails-taglibs.html
http://blogs.bytecode.com.au/glen/2008/03/12/mockfor-march—unit-testing-grails-controllers.html
http://blogs.bytecode.com.au/glen/2008/03/27/mockfor-march—unit-testing-grails-services.html

Groovy Testing guide:
http://docs.codehaus.org/display/GROOVY/Testing+Guide

Make.Go.Now – Ode to MockFor(March)
http://www.make-go-now.com/2009/03/05/ode-to-mockformarch-part-1-testing-constraints/
http://www.make-go-now.com/2009/04/17/ode-to-mockformarch-part-2-mockdomain/

IBM developerWorks:
Mastering Grails: Testing your Grails application
Mastering Grails Series

Delicous in general and my stream
http://delicious.com/
http://delicious.com/fsalbrechter

If you have any suggestions or found an error I’d be happy if you drop me a line.

Categories: grails, groovy, howto Tags: , , ,
  1. September 5th, 2009 at 00:16 | #1

    I don’t test domain classes using mocks, you’re just testing the mocking framework with code like this. I prefer to use integration tests for domain class tests, and use mocks only when testing classes that depend on domain classes (controllers, services, etc.) since I know that they work since they’re already tested.

  2. September 5th, 2009 at 09:17 | #2

    Hi Burt! Thanks for your reply! Normally the delete operation would be somewhere deep in a controller, as it was in my real case. So you think it makes no sense mocking domain classes and unit testing them? The testing plugin would be pretty useless then…? Indeed you rely on the testing framework but it has been tested itself. Don’t you have to trust in an implementation when using a framework? IMHO I think you can test business logic quite good! I’m quite new to that topic so thanks for your feedback!

  3. September 6th, 2009 at 18:57 | #3

    I didn’t say I don’t test domain classes, I said I use integrations tests for those. I want them to run in a database, otherwise their persistence isn’t really tested, only constraints, etc. to the extent that the testing plugin has implemented that. How would you test domain class methods that use HQL or Criteria queries using mocks?

    Having tested the domain classes, then I’m comfortable mocking them out to test controllers, services, etc. since they’re tested and just a dependency – I’m only concerned that the controller logic is correct when testing controllers, I don’t worry about the domain classes and just mock out their return values.

  4. September 7th, 2009 at 19:55 | #4

    To be honest it was not my decision but a requirment to unit test those classes. After playing around a bit I encountered some situations where i needed to rewrite the controller code to make the test pass which I think is really annoying and error-prone. Also I had some issues regarding dynamic methods like countBy* which is not provided in current version but has been added to git head by Graeme recently. I would also favour integration tests I think.

  5. September 7th, 2009 at 19:56 | #5

    Oh yes, I actually ran into that problem with Criteria and HQL!

  6. September 9th, 2009 at 02:56 | #6

    Minor, but my preference is to separate testing domain object constraints using unit tests and then use integration tests for queries/HQL and anything else really.
    j pimmel´s last blog ..Selenium has evolved; enter Bromine My ComLuv Profile

  1. No trackbacks yet.
CommentLuv Enabled