Unit testing 使用spock mocking或grails mockFor进行单元测试:空指针异常
我正在对我老板写的一些代码进行单元测试。他是个新手,我是TDD的新手,所以请和我一起集思广益 我要测试的文件,EmailAssist是此处未显示的服务的助手类。EmailAssist应参考其他几种服务,包括sectionService,如图所示。Unit testing 使用spock mocking或grails mockFor进行单元测试:空指针异常,unit-testing,grails,groovy,mocking,spock,Unit Testing,Grails,Groovy,Mocking,Spock,我正在对我老板写的一些代码进行单元测试。他是个新手,我是TDD的新手,所以请和我一起集思广益 我要测试的文件,EmailAssist是此处未显示的服务的助手类。EmailAssist应参考其他几种服务,包括sectionService,如图所示。 class EmailAssist { def sectionService //condensed to relevant items List<CommonsMultipartFile>
class EmailAssist {
def sectionService
//condensed to relevant items
List<CommonsMultipartFile> attachments
Map emailMap =[:]
User user
Boolean valid
public EmailAssist(){
valid = false
}
public EmailAssist(GrailsParameterMap params, User user){
//irrelevant code here involving snipped items
this.setSections(params.list('sections'))
//series of other similar calls which are also delivering an NPE
}
//third constructor using third parameter, called in example but functionally
//similar to above constructor.
//definition of errant function
void setSections(List sections) {
emailMap.sections = sectionService.getEmailsInSectionList(sections, user)
}
准确的净现值如下所示:
java.lang.NullPointerException:无法对null对象调用方法getEmailsInSectionList()
在
这是我的setSections函数的主体,对于那些在家里玩游戏的人来说。NPE堆栈起源于我的测试文件中的构造函数调用。我也尝试过使用spock风格的模拟,但该服务仍然被认为是空的。最糟糕的是,构造函数甚至不是这个测试应该测试的东西,它只是拒绝通过这个测试,结果使测试无法运行
如果有任何更多的细节,我可以提供澄清的事情,让我知道,谢谢
编辑:我短路了构造函数中的setter来完成测试,但这在测试覆盖率中留下了一些明显的漏洞,我无法找出如何修复。也许我的嘲弄放错地方了?斯波克的模拟文档对于复杂的函数来说不是很方便。你得到了NPE,因为
EmailAssist
中的sectionService
没有被创建。EmailAssist
的构造函数调用sectionService.getEmailsInSectionList(sections,user)
并且因为sectionService
为空,所以您得到一个NPE
尽管您在testsetup()
中创建了一个名为sectionService
的模拟,但它不会自动连接/注入到EmailAssist
类中。在Grails单元测试中,自动连接非常有限——文档中没有非常清楚地说明实际创建了哪些bean(而且我对Grails/Groovy比较陌生)
您需要在创建emailAssist
时插入部分服务
,否则就太晚了,无法退出NPE
如果在单元测试中将对构造函数的调用修改为:
@TestMixin(GrailsUnitTestMixin)
class EmailAssistSpec extends Specification {
@Shared
GrailsParameterMap params
@Shared
GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest()
@Shared
User user
@Shared
def mockSectionService = Mock(SectionService)
def setup() {
user = new User(id: 1, firstName: "1", lastName: "1", username: "1", email: "1@1.com")
}
def cleanup(){
}
void testGetFiles(){
given: "an EmailAssist class with an overridden constructor"
EmailAssist.metaClass.constructor = { ParamsType params, RequestType request, UserType user ->
def instance = new EmailAssist(sectionService: mockSectionService)
instance // this returns instance as it's the last line in the closure, but you can put "return instance" if you wish
}
// note that I've moved the population of the request to the given section
//bunch of code to populate request
params = newGrailsParameterMap([:], request)
// this is the list of parameters that you expect sectionService will be called with
def expectedSectionList = ['some', 'list']
when: "we call the constructor"
EmailAssist assist = new EmailAssist(params, request, user)
then: "sectionService is called by the constructor with the expected parameters"
1 * mockSectionService.getEmailsInSectionList(expectedSectionList, user)
// replace a parameter with _ if you don't care about testing the parameter
这个答案是根据伯特·贝克维思(Burt Beckwith)的博客文章得出的。我将要检查一下。我只是想说我已经发现了问题所在,我会看看我是否能让它工作,并告诉你我想到了什么。没有NPE,但似乎mockSectionService从未被调用过。即使我把它降格为“1*_”,它也会抱怨调用太少。不知道该怎么想。仅仅在一周后再详细说明一下:这个解决方案在某种程度上起了作用。目前还不清楚有多少构造函数被覆盖,坦率地说,由于元类工作方式的挑剔,我们放弃了该实现。具体地说,对值进行元分类本身是不一致的。例子
@TestMixin(GrailsUnitTestMixin)
class EmailAssistSpec extends Specification {
@Shared
GrailsParameterMap params
@Shared
GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest()
@Shared
User user
@Shared
def sectionService
def setup() {
user = new User(id: 1, firstName: "1", lastName: "1", username: "1", email: "1@1.com")
def sectionServiceMock = mockFor(SectionService)
sectionServiceMock.demand.getEmailsInSectionList() {
[]
}
sectionService = sectionServiceMock.createMock()
}
def cleanup(){
}
void testGetFiles(){
when:
//bunch of code to populate request
params = newGrailsParameterMap([:], request)
EmailAssist assist = new EmailAssist(params, request, user)
//Above constructor call generates NPE
emailMap.sections = sectionService.getEmailsInSectionList(sections, user)
@TestMixin(GrailsUnitTestMixin)
class EmailAssistSpec extends Specification {
@Shared
GrailsParameterMap params
@Shared
GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest()
@Shared
User user
@Shared
def mockSectionService = Mock(SectionService)
def setup() {
user = new User(id: 1, firstName: "1", lastName: "1", username: "1", email: "1@1.com")
}
def cleanup(){
}
void testGetFiles(){
given: "an EmailAssist class with an overridden constructor"
EmailAssist.metaClass.constructor = { ParamsType params, RequestType request, UserType user ->
def instance = new EmailAssist(sectionService: mockSectionService)
instance // this returns instance as it's the last line in the closure, but you can put "return instance" if you wish
}
// note that I've moved the population of the request to the given section
//bunch of code to populate request
params = newGrailsParameterMap([:], request)
// this is the list of parameters that you expect sectionService will be called with
def expectedSectionList = ['some', 'list']
when: "we call the constructor"
EmailAssist assist = new EmailAssist(params, request, user)
then: "sectionService is called by the constructor with the expected parameters"
1 * mockSectionService.getEmailsInSectionList(expectedSectionList, user)
// replace a parameter with _ if you don't care about testing the parameter