Spock - 模拟外部服务

Spock - mocking external service

我是 spock 框架测试的新手,我没有找到任何可以找到所需信息的示例。因此,我认为最好的方法是展示一个我需要的例子。

  1. 例如在 spock 中测试 class:

    def "getData" (){ // this is test of getData method from ExternalService
      when:
        Result result = externalService.getData()
      then:
        result.msg = 'SUCCESS'
    }
    
  2. 服务Class:

    public class ExternalService(){
      private ServiceConnector serviceConnector;
    
      public Result getData(){
        Result result = serviceConnector.callAndGet(); 
        prepareInformation(data);
        updateStatuses(data);
        return result;
      }
    }
    
  3. Class 数据作为域 Class:

    public class Data {
      private String msg
      private int Id
      // +getters/setters
    }
    

现在我有 getData 测试并且想模拟唯一的方法 callAndGet()。这意味着每次我调用 callAndGet 时,我都需要有带有 msg SUCCESS 的对象数据,但应该正常调用 getData 方法中的所有其他方法。

是不是很好理解?问题是我们如何 inject/mock 服务 class ExternalService 进入 spock 测试 class?

你需要做的是模拟 ServiceConnector class 并通过构造函数传递它(例如)。见下文:

@Grab('org.spockframework:spock-core:1.0-groovy-2.4')
@Grab('cglib:cglib-nodep:3.1')


import spock.lang.*

class Test extends Specification {
    def 'some spec'() {
        given:    
            def serviceConnector = Mock(ServiceConnector) {
                callAndGet() >> new Result(msg: 'SUCCESS')
            }
            def externalService = new ExternalService(serviceConnector)

        when:
            Result result = externalService.getData()

        then:
            result.msg == 'SUCCESS'
    }
}

public class ExternalService {
  private ServiceConnector serviceConnector

  public ExternalService(ServiceConnector serviceConnector) {
      this.serviceConnector = serviceConnector
  }

  public Result getData() {
    Result result = serviceConnector.callAndGet()

    prepareInformation(result)
    updateStatuses(result)
    result
  }

  private void prepareInformation(Result data) {
  }

  private void updateStatuses(Result data) {
  }
}

public class ServiceConnector {
    public Result callAndGet() {

    }    
}

public class Result {
    String msg
}

您不应该试图嘲笑您的服务 "only one method"。这只是糟糕设计的标志,您的代码不可测试。您应该更好地隔离 class 与小型服务的依赖关系,并在单元测试中模拟此服务。然后通过集成测试和依赖项的完整实现来测试上层。

在您的示例中,ServiceConnector 应该是一个可以轻松模拟的接口。有了这个条件,测试可以写成:

def "test a mocked service connector"() {
  given: "a service connector"
    def serviceConnector = Mock(ServiceConnector)

 and: "an external service"
   def externalService = new ExternalService(serviceConnector:serviceConnector)

 when: "Data is loaded"
   def result = externalService.data

 then: "ServiceConnector is called"
   serviceConnector.callAndGet() >> new Result(msg:"SUCCESS")

 and: "Data is mocked"
   result.msg == "SUCCESS"
}

但是,如果 ServiceConnector 是 class 并且您无法更改它,则可以在 Spock 中使用部分模拟。这种测试很难维护,并且会产生很多副作用:

def "test a mocked service connector"() {
  given: "a service connector"
    def serviceConnector = Spy(ServiceConnector) {
      callAndGet() >> new Result(msg:"SUCCESS")
    }

 and: "an external service"
   def externalService = new ExternalService(serviceConnector:serviceConnector)

 when: "Data is loaded"
   def result = externalService.data

 then: "Data is mocked"
   result.msg == "SUCCESS"
}