简体   繁体   中英

Grails 2.4.4 : How do mock a transient service inside a domain?

I have been trying to figure this out for 2 days now and I am really stuck and frustrated. I have a domain object with a service which is being used for custom validation. The domain looks like this:

class Llama { 
String name
transient myFetcherService

static transients = [
            'myFetcherService'
    ]

static constraints = {
        name validator: { val, obj ->
            if (obj.nameExists(val) == true) {
                //return some error here.
            }
        }
    }

protected boolean nameExists(String name) {
        List<Llama> llamasList = myFetcherService.fetchExistingLlamasByName(name)

        if (llamasList.isEmpty()) {
            return false
        }

        return true
    }
}

Now, I have another Service, which simply saves a list of Llama objects. It looks like this:

class LlamaFactoryService {

   public void createLlamas(List<String> llamaNames) {
     llamaNames.each { name ->
         new Llama(name: name).save()
     }
   }

}

In my test. I keep getting this error:

Failure:  createLlamas should create Llammas (com.myLlamaProject.LlamaFactoryServiceSpec)
|  java.lang.NullPointerException: Cannot invoke method myFetcherService on null object

I don't understand. In my tests, added a metaClass for the service in the "given" section. When it tries to save, it's telling that the service is null. This is what my test looks like:

given:
def myFetcherService = mockFor(MyFetcherService)   
myFetcherService.demand.fetchExistingLlamasByName {def name -> return []}
Llama.metaClass.myFetcherService = myFetcherService.createMock()

when:
service.createLlamas(['Pablo','Juan','Carlos'])

then:
//some validations here....

I also tried using metaClass on the method nameExists() like:

Llama.metaClass.myFetcherService = { def name -> false }

, but it gives me the same nullPointerException as the one above. Could someone point me to the right direction? I'm a bit stuck. :(

Thanks in advance for reading and helping.

You're using a unit test and the general rule for unit tests is that beans generally aren't created for you, so you'll need to inject them yourself.

(Code edited to reflect the fact I misread the question) I think you want a testing pattern something like:

given:
def mockMyFetcherService = Mock(MyFetcherService) // create the mock
Llama.metaClass.getMyFetcherService = { mockMyFetcherService } // inject the dependency
def returnList = []  // let's just define this here and you can re-use this pattern in other tests more easily

when:
service.createLlamas(['Pablo','Juan','Carlos'])

then:
// tell Spock what you expect to have happen with your Mock - return the List you defined above
3 * mockFetcherService.fetchExistingLlamasByName(_) >> returnList

If the injection of the service into the metaClass doesn't work ( suggested here ), you could always try using the defineBeans{} closure within the unit test itself ( http://www.block-consult.com/blog/2011/08/17/inject-spring-security-service-into-domain-class-for-controller-unit-testing/ ).

Thus you could try:

defineBeans {
  myFetcherService(MockMyFetcherService)
}

where MockMyFetcherService is defined in the same file that defines the test. This is the approach followed here :

See here for examples of more Spock interactions.

If you're using Grails 2.4.3 or below you'll need to put CGLIB in BuildConfig.groovy but I see here that it's already done for you in 2.4.4, so you should be ok just to use Mock(classname) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM