我正在进行单元测试我老板写的一些代码。他正在画一个空白,我是TDD的新手,所以请和我一起头脑风暴。
我要测试的文件,EmailAssist是此处未显示的服务的帮助程序类。 EmailAssist应该引用其他几个服务,包括sectionService,如图所示。
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)
}
正在调用的SectionService部分如下所示。
Set<String> getEmailsInSectionList(List<String> sections, User user) {
if(sections && user){
//code to call DB and update list
}
else{
[]
}
我的测试没有提供一个部分,所以这应该返回一个空列表,特别是因为我甚至无法在单元测试中访问数据库。
单元测试如下。这是使用mockFor,因为spock的模拟功能似乎不是我需要的。
@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
确切的NPE如下: java.lang.NullPointerException:无法在null对象上调用方法getEmailsInSectionList() 在
emailMap.sections = sectionService.getEmailsInSectionList(sections, user)
对于那些在家里玩的人来说,这是我的setSections函数的主体。 NPE堆栈起源于我的测试文件中的构造函数调用。我也尝试使用spock风格的模拟,但该服务仍被视为null。最糟糕的是,构造函数甚至不是这个测试应该测试的,它只是拒绝传递它,因此使得测试无法运行。
如果有更多细节我可以提供澄清事项,请告诉我,谢谢!
编辑:我在构造函数中将setter短路以完成测试,但是在测试覆盖中留下了一些明显漏洞,我无法弄清楚如何修复。也许我的嘲笑位于错误的地方? Spock的模拟文档对于复杂的函数来说并不是很方便。
答案 0 :(得分:1)
您正在获取NPE,因为sectionService
中的EmailAssist
未创建。 EmailAssist
调用sectionService.getEmailsInSectionList(sections, user)
并且sectionService
为{null}的构造函数为null,即可获得NPE。
虽然您在测试sectionService
中创建了一个名为setup()
的模拟,但它并没有自动连线/注入EmailAssist
类。 Grails单元测试中的自动接线非常有限 - 文档并不清楚实际创建的bean(并且我对Grails / Groovy来说相对较新)。
您需要在创建sectionService
时注入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 here的博客文章。