Grails. Testing command objects with spring beans inside

315 Views Asked by At

I have a command object with constraints using spring beans in custom validator:

class UserPasswordCommand {
    String currentPassword
    //other fields....
    static constraints = {
        currentPassword validator: { currPass, upc ->
            Holders.applicationContext.passwordEncoder.isPasswordValid(
                    Holders.applicationContext.springSecurityService.currentUser.password, currPass, null)
        }
    }
}

But when invoking new UserPasswordCommand(...) in unit test I get the following:

java.lang.NullPointerException: Cannot get property 'currentUser' on null object

So it appears that springSecurityService = null (as expected). I tried different actions to mock or "metaClass" it but unsuccessful.

Please advise if there is a better way to use beans from applicationContext in command objects or some approaches of mocking beans in Holders.applicationContext.

Thanks in advance!

UPDATE

Placed the following to setup() section:

def setup() {
    def appContext = Mock(ApplicationContext)
    def springSecurityService = Mock(SpringSecurityService)
    appContext.springSecurityService >> springSecurityService
    Holders.metaClass.static.applicationContext = { appContext }
}

But no effect. springSecurityService is null in applicationContext retrieved from Holders. What am I doing wrong?

2

There are 2 best solutions below

0
Anton Hlinisty On BEST ANSWER

I resolved the issue by getting rid of Holders in the original command object:

class UserPasswordCommand {
    static passwordEncoder
    static springSecurityService
    String currentPassword
    //...
    static constraints = {
        currentPassword validator: { currPass, upc ->
    passwordEncoder.isPasswordValid(springSecurityService.currentUser.password, currPass, null)
        }
    //...
    }
}

Added the mocks/stubs to test test:

def springSecurityService = Mock(SpringSecurityService)
def passwordEncoder = Mock(PasswordEncoder)

def setup() {
    passwordEncoder.isPasswordValid(_, _, _) >> Boolean.TRUE
    springSecurityService.currentUser >> Mock(User)
}

and:

given:
Map props = [
    currentPassword: CURRENT_PASSWORD,
    passwordEncoder: passwordEncoder,
    springSecurityService: springSecurityService,
]

expect:
new UserPasswordCommand(props).validate()
3
rgrebski On

You can override Holder.applicationContext like this:

  def setup() {
    ApplicationContext appContext = Mock()
    PasswordEncoder passwordEncoder = Mock()
    passwordEncoder.isPasswordValid(_, _, _) >> true

    appContext.passwordEncoder >> passwordEncoder //you can do the same for springSecurityService

    //override Holder.getAplicationContext() method to return mocked context
    Holders.metaClass.static.applicationContext = { appContext }
}