0

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?

Anton Hlinisty
  • 1,441
  • 1
  • 20
  • 35
  • Just FYI using Holders in a command object is a really bad idea. It will perform poorly. The better way to do it is to make the beans constructor arguments and construct the class yourself. – James Kleeh Mar 20 '17 at 05:22
  • @JamesKleeh, thanks for info. I changed retrieving from Holders to a normal DI and made the fields static. Is this solution good enough? – Anton Hlinisty Mar 20 '17 at 06:59

2 Answers2

1

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 }
}
rgrebski
  • 2,354
  • 20
  • 29
0

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()
Anton Hlinisty
  • 1,441
  • 1
  • 20
  • 35