我一直试图解决这个问题2天,我真的很困惑和沮丧。我有一个域对象,其中包含一个用于自定义验证的服务。域名如下:
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
}
}
现在,我有另一个服务,它只保存一个Llama对象列表。它看起来像这样:
class LlamaFactoryService {
public void createLlamas(List<String> llamaNames) {
llamaNames.each { name ->
new Llama(name: name).save()
}
}
}
在我的测试中。我一直收到这个错误:
Failure: createLlamas should create Llammas (com.myLlamaProject.LlamaFactoryServiceSpec)
| java.lang.NullPointerException: Cannot invoke method myFetcherService on null object
我不明白。在我的测试中,在“给定”部分为服务添加了一个metaClass。当它试图保存时,它告诉该服务为空。这就是我的测试结果:
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....
我也尝试在方法nameExists()
上使用metaClass,如:
Llama.metaClass.myFetcherService = { def name -> false }
,但它给了我与上面相同的nullPointerException。有人能指出我正确的方向吗?我有点卡住了。 :(
提前感谢阅读和帮助。
答案 0 :(得分:0)
您正在使用单元测试,单元测试的一般规则是通常不会为您创建bean,因此您需要自己注入它们。
(编辑的代码反映了我误解了这个问题的事实) 我想你想要一个类似的测试模式:
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
如果将服务注入到metaClass中不起作用(suggested here),您总是可以尝试在单元测试中使用defineBeans {}闭包(http://www.block-consult.com/blog/2011/08/17/inject-spring-security-service-into-domain-class-for-controller-unit-testing/)。
因此你可以尝试:
defineBeans {
myFetcherService(MockMyFetcherService)
}
其中MockMyFetcherService
在定义测试的同一文件中定义。这是方法followed here:
有关更多Spock交互的示例,请参阅here。
如果您使用Grails 2.4.3或更低版本,则需要将CGLIB放入BuildConfig.groovy中,但我看到here已经在2.4.4中为您完成了,所以您应该没问题使用Mock(classname)
。