【问题标题】:Grails / Spock: How to mock single method within class where method is called from within the class itself?Grails / Spock:如何在类中模拟从类本身调用方法的单个方法?
【发布时间】:2015-02-11 07:25:28
【问题描述】:

鉴于以下情况,我如何使用 Spock 模拟 processMessage(),以便我可以检查 processBulkMessage() 是否调用了 processMessage() n 次,其中 n 是 BulkMessage 中的消息数?

class BulkMessage {
    List messages
}

class MyService {

    def processBulkMessage(BulkMessage msg) {
        msg.messages.each {subMsg->
            processMessage(subMsg)
        }
    }

    def processMessage(Message message) {

    }
}

【问题讨论】:

    标签: unit-testing grails groovy spock


    【解决方案1】:

    您可以使用 spies 和部分模拟(需要 Spock 0.7 或更高版本)。

    创建间谍后,您可以监听调用者与间谍背后的真实对象之间的对话:

    def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])
    subscriber.receive(_) >> "ok"
    

    有时,希望同时执行一些代码并委托给真正的方法:

    subscriber.receive(_) >> { String message -> callRealMethod(); message.size() > 3 ? "ok" : "fail" }
    

    【讨论】:

      【解决方案2】:

      在我看来,这不是一个设计良好的解决方案。测试和设计齐头并进 - 我建议 this 交谈以更好地调查它。如果需要检查是否在正在测试的对象上调用了其他方法,则似乎应该将其移至具有不同职责的其他对象。

      我会这样做。我知道可见性在 groovy 中是如何工作的,所以请注意 cmets。

      @Grab('org.spockframework:spock-core:0.7-groovy-2.0')
      @Grab('cglib:cglib-nodep:3.1')
      
      import spock.lang.*
      
      class MessageServiceSpec extends Specification {
      
          def 'test'() {
              given:
              def service = new MessageService()
              def sender = GroovyMock(MessageSender)
      
              and:
              service.sender = sender
      
              when:
              service.sendMessages(['1','2','3'])
      
              then:
              3 * sender.sendMessage(_)
          }
      }
      class MessageSender { //package access - low level   
         def sendMessage(String message) {
            //whatever
         }
      }
      
      class MessageService {
      
         MessageSender sender //package access - low level
      
         def sendMessages(Iterable<String> messages) {
            messages.each { m -> sender.sendMessage(m) }
         }
      }
      

      【讨论】:

      • 非常感谢 - 自从我提出这个问题以来,我刚刚重新审视了这一点,并且学到了很多关于设计的知识。我现在同意你的看法,更好的设计是bulkMessageProcessingService 和individualMessageProcessingService。因此,使用模拟进行测试是微不足道的。
      【解决方案3】:

      它不使用 Spock 内置的 Mocking API(不确定如何部分模拟对象),但这应该可以解决问题:

      class FooSpec extends Specification {
      
          void "Test message processing"() {
              given: "A Bulk Message"
              BulkMessage bulk = new BulkMessage(messages: ['a', 'b', 'c'])
      
              when: "Service is called"
              def processMessageCount = 0
              MyService.metaClass.processMessage { message -> processMessageCount++ }
              def service = new MyService()
              service.processBulkMessage(bulk)
      
              then: "Each message is processed separately"
              processMessageCount == bulk.messages.size()
          }
      }
      

      【讨论】:

        【解决方案4】:

        对于在 Spock 中测试的 Java Spring 人员:

        constructorArgs 是要走的路,但要使用构造函数注入。 Spy() 不会让你直接设置自动装配字段。

        // **Java Spring**
        class A {
            private ARepository aRepository;
        
            @Autowire
            public A(aRepository aRepository){
                this.aRepository = aRepository;
            }
        
            public String getOne(String id) {
                tryStubMe(id)  // STUBBED. WILL RETURN "XXX"
                ...
            }
        
            public String tryStubMe(String id) {
                return aRepository.findOne(id)
            }
        
            public void tryStubVoid(String id) {
                aRepository.findOne(id)
            }
        }
        

        // **Groovy Spock**
        class ATest extends Specification {
        
            def 'lets stub that sucker' {
                setup:
                    ARepository aRepository = Mock()
                    A a = Spy(A, constructorArgs: [aRepository])
                when:
                    a.getOne()
                then:
                    // Stub tryStubMe() on a spy
                    // Make it return "XXX"
                    // Verify it was called once
                    1 * a.tryStubMe("1") >> "XXX"
            }
        }      
        

        Spock - 在 Spy 对象上存根 void 方法

        // **Groovy Spock**
        class ATest extends Specification {
        
            def 'lets stub that sucker' {
                setup:
                    ARepository aRepository = Mock()
                    A a = Spy(A, constructorArgs: [aRepository]) {
                        1 * tryStubVoid(_) >> {}
                    }
                when:
                    ...
                then:
                    ...
            }
        } 
        

        【讨论】:

          猜你喜欢
          • 2017-12-29
          • 1970-01-01
          • 1970-01-01
          • 2016-12-17
          • 2019-08-29
          • 2020-05-23
          • 2018-01-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多