Injecting a mock service into a domain class for a Grails unit test?
Asked Answered
I

1

7

I am writing some Spock spec based unit tests under Grails 2.1.1. I'm having trouble getting springSecurityService injected into my domain object that is used by my unit.

This is what I have so far,

@Mock([SecUser])
@TestFor(FooService)
class FooServiceSpec extends Specification {

    def "test some stuff"() {
        given:
        def mockSecUserService = Mock(SecUserService)
        mockSecUserService.emailValid(_) >> { true }
        mockSecUserService.checkUsername(_) >> { null }
        service.secUserService = mockSecUserService

        def mockSpringSecurityService = Mock(SpringSecurityService)
        mockSpringSecurityService.encodePassword(_) >> { 'tester' }
        // FIXME this needs to be injected somehow

        def params = [
                email: '[email protected]',
                username: 'unittester',
                password: 'tester',
                password2: 'tester'
        ]

        when:
        def result = service.createUser(params)

        then:
        // test results
    }
}

So what happens is that my service being test throws a NullPointerException because the mockSpringSecruityService is not injected. The createUser call in my service validates some params and then creates a SecUser domain object.

The SecUser has the following service injected into it to support password encoding,

class SecUser {
    transient springSecurityService

What I can't figure out out is inject this service in way that it is available to the domain class. I know there are a few posts already relating to this subject but most of them assume the test can instantiate the domain object, but in my case the instantiation is part of the unit being testing.

I've tried the following in place of the FIXME,

defineBeans {
    mockSpringSecurityService(SpringSecurityService) { bean -> 
        bean.autowire = true
    }
}

But as a result I get the follow error,

| groovy.lang.MissingMethodException: No signature of method: grails.plugins.springsecurity.SpringSecurityService.call() is applicable for argument types: (java.lang.Class, skillz.GameAccountServiceSpec$_spock_feature_0_0_closure4_closure5) values: [class grails.plugins.springsecurity.SpringSecurityService, ...] Possible solutions: wait(), any(), find(), dump(), collect(), grep()

Any ideas?

Irreversible answered 8/8, 2013 at 17:31 Comment(3)
Have you also tried using metaClass? like SecUser.metaClass.springSecurityService = mockSpringSecurityService. BTW def springSecurityService can also be used instead of transient springSecurityService I guess. (Anything with type def should be transient by default in domain class)Dilatory
Can you try this similar way? It is not helping me to solve the issue though.Dilatory
@dmahapatro, yeah that was one of the many articles I tried to no avail, I think that particular article is geared at older grails. I'm having some luck with metaclass approach and I will post something shortly.Irreversible
I
6

What I ended up doing as a work around is,

SecUser.metaClass.encodePassword = { null }

In my domain object encodePassword is the method that uses the springSecurityService bean. So if I understand this correctly, this just overwrites this method to do nothing (the method is a void).

I'd prefer to use the metaClass to faux inject the bean, but my attempt at

SecUser.metaClass.getSpringSecurityService = { mockSpringSecurityService }

Blows up with an NPE when encodePassword attempts to use SpringSecurityService.

I feel like this is a less than ideal work around, so I'd love for someone to give a less hacky answer.

Irreversible answered 8/8, 2013 at 21:6 Comment(1)
That is a by pass I agree. I did not want to give that as an answer. If you have mocked springSecurityService then use the mocked object in the metaclassed encodePassword. Something like SecUser.metaClass.encodePassword = {-> mockSpringSecurityService.encodePassword } since it is already mocked to tester as shown in question.Dilatory

© 2022 - 2024 — McMap. All rights reserved.