Grails articles (rss)

Unit testing with SpringSecurityUtils

Unit testing controllers is often overlooked in favour of Integration testing simply because of all the services you need to mock out while testing, that is, it's simply easier to do Integration tests when the mocked Domain classes etc. are not as full featured as the real ones. The trouble is unit tests are generally much faster, they don't rely on resources like DB and specify the tests a lot better, you are only testing this bit, not the services.

So it is desirable in many cases to be able to unit test a controller where possible. Spring Security is one place that people get hung up when unit testing. For example if you have controller action that looks something like this:-

def dashboard = {
if (SpringSecurityUtils.ifAllGranted("ROLE_ADMIN")) {
return redirect(controller: "admin", action: "index")
}
...

You need to mock out SpringSecurityUtils.ifAllGranted which is a static method, otherwise it will try and get a SecurityContext etc. etc. which you just don't need for this test.

So to mock this out just use the metaClass and replace the static method like so:-

SpringSecurityUtils.metaClass.'static'.ifAllGranted = { String role ->
return false
}

Now you control that method however this "fix" comes with a WARNING: when you replace a method this way it stays replaced while the JVM is alive, so if you have functional tests that rely on this method and just use grails test-app to do all tests chances are your functional tests will fail.

To remove your meta programming methods you would have to do something like this in tearDown() :-

protected void tearDown() {
super.tearDown()
SpringSecurityUtils.metaClass = null;
}

However there is a much neater way of doing it shown here -> http://www.jworks.nl/blog/show/75/remove-groovy-metaclass-methods. (thanks Marcin Erdmann and Erik Pragt!)

What you do is call registerMetaClass(Thing.class) in your unit tests setUp() and define the metaclass changes then the metaClass will be cleaned up for you on tearDown(). It'd look like this:-

protected void setUp() {
super.setUp()
registerMetaClass(SpringSecurityUtils)
}

enjoy.
--

Adding bindData() to a Service

Sometimes when you don't want to repeat yourself in controllers you need to add some support code to a grails Service. If you do this then you quickly reach the point where you'd really like bindData() to be available on a Service. JT's blog has a nice little teaser there, but it's not quite the same as having bindData() just being available, so how do you do it?

A. use GroovyDynamicMethodsInterceptor

Here is a trivial  example:
package com.nerderg.serviceExample
import org.codehaus.groovy.grails.web.metaclass.BindDynamicMethod
import org.codehaus.groovy.grails.commons.metaclass.GroovyDynamicMethodsInterceptor

class CommandSupportService {
    static transactional = true

    CommandSupportService() {
        GroovyDynamicMethodsInterceptor i = new GroovyDynamicMethodsInterceptor(this)
        i.addDynamicMethodInvocation(new BindDynamicMethod())
    }

def populateCommand(params) {
def myCommand = new myCommand()
bindData(myCommand, params)
return myCommand
}
So what we're looking at here is adding the DynamicMethod BindDynamicMethod to a DynamicMethodInterceptor added to our service class when it's constructed.

More testing Grails Spring Security

One thing I missed in my last article on integration testing with Grails Spring Security plugin was a test to see that you weren't authenticated, or couldn't access the controller method. So I put that test in and was surprised that I could get to the controller! So the Secure controller looked like this:

package com.nerderg.tester

import grails.plugins.springsecurity.Secured
class SecureController {

    @Secured(['ROLE_ADMIN'])
    def index = {
            render "Secure access only"
    }
}

Of course this is because the test code calls index() directly, missing the security filter. That means you can unit test your annotated controllers function sans security if you want, but it makes it hard to test the security unless you use a web test (WebDriver/Geb).

So what use is the technique I described previously? We'll if you use the SpringSecurityUtil functions you still need to be authenticated. For example if your controller looked like this:

package com.nerderg.tester

import grails.plugins.springsecurity.Secured
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils

class SecureController {

    def index = {
        if(SpringSecurityUtils.ifAllGranted("ROLE_ADMIN")){
            render "Secure access only"
        } else {
            render "Please log in"
        }
    }
}

In this case the test will fail if you're not logged in, and just to be sure that it fails here is the updated test code:

...
void testIndexNotLoggedIn() {
controller.index()
println controller.response.contentAsString
assert "Please log in" == controller.response.contentAsString
}
...
void testIndexLoggedIn() {
def authToken = new UPAT("me", "password")

try {
authenticationManager.authenticate(authToken)
} catch(e) {
fail("exception $e should not be thrown")
}

controller.index()
println controller.response.contentAsString
assert "Secure access only" == controller.response.contentAsString
}

BUT this test will fail because I needed to do one more thing to authenticate the user. I need to set the Authentication in the SecurityContext. So here is the full working test code:

package com.nerderg.tester

import grails.test.*
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken as UPAT
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.context.SecurityContextHolder;

class SecureControllerIntegrationTests extends ControllerUnitTestCase {

    def authenticationManager

    protected void setUp() {
        super.setUp()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testIndexNotLoggedIn() {
        controller.index()
        println controller.response.contentAsString
        assert "Please log in" == controller.response.contentAsString
    }

    void testIndexLoggedIn() {
        def authToken = new UPAT("me", "password")

        try {
            def auth = authenticationManager.authenticate(authToken)
            SecurityContextHolder.getContext().setAuthentication(auth)
        } catch(e) {
            fail("exception $e should not be thrown")
        }

        controller.index()
        println controller.response.contentAsString
        assert "Secure access only" == controller.response.contentAsString
    }
}

Note the SecurityContextHolder.getContext().setAuthentication(auth) in that code. It pays to not skimp on your tests! It also pays to look at the source SpringSecurityUtils.java.




Copyright © nerdErg Pty Ltd 2012 ABN 20 159 294 989