2015-03-11 5 views
3

Я работаю над модулем тестирования кода, написанного моим боссом. Он рисует пробел, и я новичок в TDD, так что мозговой штурм со мной, пожалуйста.Unit Testing с использованием spock mocking или grails mockFor: Исключение нулевого указателя

Мой файл для проверки, 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: "[email protected]") 
     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: Невозможно вызвать метод getEmailsInSectionList() на нулевой объект на

 emailMap.sections = sectionService.getEmailsInSectionList(sections, user) 

Который есть тело моей функции setSections, для те из вас, кто играет дома. Стек NPE возникает в вызове конструктора в моем тестовом файле. Я также пробовал использовать spock-style mocking, но служба по-прежнему считается нулевой. Худшая часть заключается в том, что конструктор не является тем, что этот тест должен тестировать, он просто отказывается передать это и в результате сделать тесты невозможными для запуска.

Если есть более подробная информация, которую я могу предоставить, чтобы прояснить ситуацию, дайте мне знать, спасибо!

Редактировать: Я коротко замыкал сеттеры в конструкторе для завершения тестов, но это оставляет некоторые очевидные отверстия в покрытии для испытаний, что я не могу понять, как исправить. Может быть, мое издевательство находится не в том месте? Скукающая документация Спока не очень удобна для сложных функций.

ответ

1

Вы получаете NPE, потому что sectionService в пределах EmailAssist не создается. Ваш конструктор для EmailAssist звонит sectionService.getEmailsInSectionList(sections, user) и потому, что sectionService имеет значение null, вы получаете NPE.

Несмотря на то, что в тесте setup() вы создаете макет под названием sectionService, он не получает автоматическую проводку/ввод в класс EmailAssist. Вы получаете очень ограниченную автоматическую проводку в модульных тестах Grails - документация не очень ясна в отношении того, какие бобы действительно создаются (и я относительно новичок в 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: "[email protected]") 
    } 

    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 Беквит here.

+0

Я собираюсь проверить это. Я буквально только что показал, чтобы сказать, что я понял, что это проблема, я посмотрю, смогу ли я заставить это работать и рассказать вам, что я придумал. – mrfixij

+0

Нет NPE, но кажется, что mockSectionService никогда не вызывается. Даже если я откажусь от этого до «1 * _», он скулит о слишком маленьких вызовах. Не уверен, что об этом думать. – mrfixij

+0

Просто проработать чуть ли не неделю спустя: это решение работало в некотором роде. Неясно, какая часть конструктора была перезаписана и, честно говоря, мы отказались от этой реализации из-за придирки в том, как работает metaClass. В частности, метаклассирование значений несовместимо с самим собой. Пример [здесь] (http://stackoverflow.com/questions/27489785/groovy-metaclass-fails-when-overriding-method-called-in-constructor) – mrfixij

Смежные вопросы