Grails / Spock:如何在类中模拟从类本身调用方法的单个方法?

Posted

技术标签:

【中文标题】Grails / Spock:如何在类中模拟从类本身调用方法的单个方法?【英文标题】:Grails / Spock: How to mock single method within class where method is called from within the class itself? 【发布时间】: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) 

    

【问题讨论】:

【参考方案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:
            ...
    
 

【讨论】:

以上是关于Grails / Spock:如何在类中模拟从类本身调用方法的单个方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spock 控制器测试中模拟 Grails 请求对象方法

使用 Spock 进行 Grails 测试 - 选择哪个模拟框架?

Grails Spock单元测试需要模拟事务管理器

如何使用 Spock 测试 Grails 服务?

如何在类中调用函数?

IntelliJ IDEA 的零覆盖:带有 Spock 单元测试的 Grails