Unit testing 使用spock mocking或grails mockFor进行单元测试:空指针异常

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>

我正在对我老板写的一些代码进行单元测试。他是个新手,我是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)
        }
准确的净现值如下所示: java.lang.NullPointerException:无法对null对象调用方法getEmailsInSectionList() 在

这是我的setSections函数的主体,对于那些在家里玩游戏的人来说。NPE堆栈起源于我的测试文件中的构造函数调用。我也尝试过使用spock风格的模拟,但该服务仍然被认为是空的。最糟糕的是,构造函数甚至不是这个测试应该测试的东西,它只是拒绝通过这个测试,结果使测试无法运行

如果有任何更多的细节,我可以提供澄清的事情,让我知道,谢谢


编辑:我短路了构造函数中的setter来完成测试,但这在测试覆盖率中留下了一些明显的漏洞,我无法找出如何修复。也许我的嘲弄放错地方了?斯波克的模拟文档对于复杂的函数来说不是很方便。

你得到了NPE,因为
EmailAssist
中的
sectionService
没有被创建。
EmailAssist
的构造函数调用
sectionService.getEmailsInSectionList(sections,user)
并且因为
sectionService
为空,所以您得到一个NPE

尽管您在test
setup()
中创建了一个名为
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