2012-12-24

Testing Grails Filters that use Services

For SourceNode, I was writing a filter to redirect the user when the application is in "configuration-only" mode, as controlled by the AdministrationService. Let's say the code looks something like this:

grails-app/controllers/blogfiltertest/BookController.groovy:

package blogfiltertest

class BookController {
    def index() { }
}

grails-app/controllers/blogfiltertest/InitialConfigurationController.groovy:

package blogfiltertest

class InitialConfigurationController {
    def configurationRequired() {}
}

grails-app/services/blogfiltertest/AdministrationService.groovy:

package blogfiltertest

class AdministrationService {
    static transactional = false

    boolean isConfigurationOnly() {
        return false
    }
}

grails-app/conf/blogfiltertest/AdministrationFilters.groovy:

package blogfiltertest

class AdministrationFilters {
    def administrationService

    def filters = {
        configOnly(controller: 'initialConfiguration', invert: true) {
            before = {
                if (administrationService.isConfigurationOnly()) {
                    redirect(controller: 'initialConfiguration', action: 'configurationRequired')
                    return false
                } else {
                    return true
                }
            }
        }
    }
}

I wanted to write a unit test for this filter. It started something like this:

test/unit/blogfiltertest/AdministrationFiltersTest.groovy:

package blogfiltertest

import grails.test.mixin.*

@TestFor(BookController)
@Mock(AdministrationFilters)
class AdministrationFiltersTests {
    void testConfigurationOnlyRedirected() {
        withFilters(controller: 'book', action: 'index') {
            controller.index()
        }
        assert response.redirectedUrl == '/initialConfiguration/configurationRequired'
    }
}

The first roadblock I ran into was this:

java.lang.NullPointerException: Cannot invoke method isConfigurationOnly() on null object

This appears to be a Grails bug, GRAILS-8976. At least as of Grails 2.1.2, Grails unit tests don't properly perform dependency injection into filters. So, a workaround: manually have the filter get the appropriate service from the application context. This will also be run at run-time, resulting in a double-lookup of the bean definition. Here's the patched vesion:

package blogfiltertest

class AdministrationFilters {
    def administrationService

    def filters = {
        configOnly(controller: 'initialConfiguration', invert: true) {
            before = {
                administrationService = applicationContext.getBean('administrationService')
                if (administrationService.isConfigurationOnly()) {
                    redirect(controller: 'initialConfiguration', action: 'configurationRequired')
                    return false
                } else {
                    return true
                }
            }
        }
    }
}

It appears that the bean lookup needs to happen in the before closure. If you put it directly in filters, it doesn't have access to the applicationContext, and if you put it in configOnly, it won't find beans defined in the test.

Now there's a different error:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'administrationService' is defined

That's because, as per the documentation in the section "Testing Spring Beans", only a subset of the Spring beans are available for testing use, and you use defineBeans to make more available. Here's the updated version of the test using Grails mocking support to inject a mock version of the service.

package blogfiltertest

import grails.test.GrailsMock
import grails.test.mixin.*

@TestFor(BookController)
@Mock(AdministrationFilters)
class AdministrationFiltersTests {
    void testConfigurationOnlyRedirected() {
        defineBeans {
            administrationServiceControl(GrailsMock, AdministrationService)
            administrationService(administrationServiceControl: "createMock")
        }

        def administrationServiceControl = applicationContext.getBean("administrationServiceControl")
        administrationServiceControl.demand.isConfigurationOnly { true }

        withFilters(controller: 'book', action: 'index') {
            controller.index()
        }
        assert response.redirectedUrl == '/initialConfiguration/configurationRequired'
    }
}

When I ran into errors like below, it meant that I was trying to do bean lookup by type, which doesn't seem to be working with this style of mocking. The fix was to do lookup by name (which is more consistent with how Grails will do the injection anyway).

org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [blogfiltertest.AdministrationService] is defined: expected single bean but found 0:

Update 12/12/2012: If your filter uses URI scope (even if it's for all URIs) rather than controller/action, you'll need to set a URI on the request in the test for it to match using withFilters. Here's an example of what it looks like:

request.forwardURI = '/book/index'

2 comments:

Nooruddin said...

what does this statements do?

administrationServiceControl(GrailsMock, AdministrationService)
administrationService(administrationServiceControl: "createMock")

James R. Carraway said...

We are a book publisher company. We have publish different type of mathematics books for college students. You can take our book.