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

时间:2015-03-11 17:42:16

标签: unit-testing grails groovy mocking spock

我正在进行单元测试我老板写的一些代码。他正在画一个空白,我是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的模拟文档对于复杂的函数来说并不是很方便。

1 个答案:

答案 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的博客文章。