1
votes

I am trying to write unit test case for grails controller which has following structure:

class MyController{

    def save(){
         def myDomain = new MyDomain(params)
         business validation 1
         business validation 2
         myDomain.writedatasource.save()
         business validation 3
         business validation 4
    }
}

Since Unit test doesn't load Datasource.groovy the writedatasource isn't available during unit testing so the test cases for "business validation 3" and "business validation 4" fail as I get

groovy.lang.MissingPropertyException: No such property: writedatasource for class: MyDomain

How can I modify my test case to test validation scenarios 3 and 4?

Test case is simple and looks like follows:

void testSave(){
    ...setup...
    controller.save()
    assert conditions
    ....

}
2
Interesting question. You will have to mock the datasource or even better if you write instead an integration test - Fran García
Agree that integration test will certainly do the job but we have several instances of such scenarios (almost 80% of the components) so if we write integration tests for each of them then essentially there won't be any unit testing. Even if we mock datasource, how do you attach it to myDomain so that it's available to controller to make myDomain.writedatasource.save() work. - Mayank

2 Answers

1
votes

Not sure if that could make the trick, but you can try:

def 'the controller call the save method in writedatasource'() {
    given:
        def calls = 0
    and:
        def myDomainInstance = new MyDomain()
    and:
        MyDomain.metaClass.getWritedatasource = { new Object() }
    and:
        Object.metaClass.save = { Map attrs ->
            calls++
        }
    when:
        controller.save()
    then:
        calls == 1
}

But the only thing are you doing is testing that the save is called under writedatasource, so it would be better to have also an integration test. If that makes the trick, please answer as well at http://grails.1312388.n4.nabble.com/Mocking-in-a-unit-test-a-domain-object-multiple-datasources-td4646054.html as they seem to have the same problem.

1
votes

Addressing original question:

I ran into this and would have had to mock many, many more methods than just 'save' on multiple domains. So instead, I overrode the getter of my domains to simply return the invoking domain instance:

def setup() {
    [MyDomain1, MyDomain2].each { Class clazz ->
        mockDomain(clazz)
        clazz.metaClass.getWritedatasource = { 
            return delegate
        }
        clazz.metaClass.'static'.getWritedatasource = {
            return delegate
        }
    }
}

This also saves me from including @DirtiesRuntime since I'm not updating the metaClass of anything I'd want to clean.

Most importantly, whatever calls the datasource, be it the domain class or the instance, it should be decorated with GORM fanciness from mockDomain, meaning I don't have to mock any of the GORM methods.

What if you want dynamic datasources?

In my particular case, datasources are configurable and may change depending on environment. If you're in a similar situation, you can configure this in your domain mapping:

static mapping = {
    datasources    Holders.grailsApplication?.config.dynamic?.datasources
    ...
}

where dynamic.datasources is an array of datasource names. Then, in the test setup:

def setup() {
    grailsApplication.config.dynamic.datasources = ['foo', 'bar']
    [MyDomain1, MyDomain2].each { Class clazz ->
        mockDomain(clazz)
        grailsApplication.config.dynamic.datasources.each{
            clazz.metaClass."get${it.capitalize()}" = { 
                return delegate
            }
            clazz.metaClass.'static'."get${it.capitalize()}" = { 
                return delegate
            }
        }
    }
}