Spock 在 Where 块中测试异常处理

Spock testing exception handling in a Where block

我正在测试一个有一些依赖关系的服务方法;我想断言,如果这些依赖项中的任何一个抛出异常,服务方法应该 return 默认值。

我想编写的服务和测试看起来像这样。

static class Service {
    def dependency1
    def dependency2
    def dependency3

    def method() {
        try {
            def foo = dependency1.get()
            def bar = dependency2.get()
            def baz = dependency3.get()
            return " $foo $bar $baz "
        } catch (Exception e) {
            println e
            return ' default value '
        }
    }
}

def 'test Service error handling'() {
    given:
    def dependency1 = Mock(Supplier)
    def dependency2 = Mock(Supplier)
    def dependency3 = Mock(Supplier)
    def serviceUnderTest = new Service(dependency1: dependency1, dependency2: dependency2, dependency3: dependency3)

    when:
    def result = serviceUnderTest.method()

    then:
    result == ' default value '
    dependency1.get() >> closure1
    dependency2.get() >> closure2
    dependency3.get() >> closure3

    where:
    closure1                              | closure2                              | closure3
    {-> throw new Exception('closure1') } | {-> null }                            | {-> null };
    {-> null}                             | {-> throw new Exception('closure2') } | {-> null };
    {-> null}                             | {-> null}                             | {-> throw new Exception('closure3') }
}

此测试不起作用,因为它导致模拟 return 文字闭包而不是这些闭包的结果。当然这是由于添加了 where 块造成的,因为任何 mock 都可以直接 return 单个闭包的结果,即 dependency1.get() >> { throw new Exception() }

我是否被迫将其写为三个单独的测试,或者是否有其他方法可以将它们组合起来?

如果你写

dependency1.get() >> closure1

您的模拟将 return 闭包本身而不对其进行评估。评估仅发生在 GroovyString " $foo $bar $baz " 内部,将评估期间发生的错误消息扩展到其中,但不会升级该异常。

您想使用

dependency1.get() >> { closure1() }

为了修复你的测试。 () 评估您的闭包,但同时周围的闭包 {} 确保评估仅在调用存根方法时发生,而不是在定义时发生。

几点改进意见:

  • 如何展开您的测试,将其拆分为多个具有参数化名称的方法?这也有很好的副作用,可以帮助 IDE 和 Groovy 编译器解析没有分号和 { -> ... 语法的 where: 块。
  • 如何在 given: 块中而不是在它们不属于的 then: 块中存根模拟方法?
  • 如何在 mock 定义中添加方法以使测试更紧凑?
  • 如何按照 Spock 手册的建议将这个简单案例中的 when: ... then: 替换为 expect:
package de.scrum_master.Whosebug.q57172322

import spock.lang.Specification
import spock.lang.Unroll

class ServiceDependenciesThrowingErrorsTest extends Specification {

  @Unroll
  def 'handle error in service #serviceName'() {
    given:
    def serviceUnderTest = new Service(
      dependency1: Mock(Supplier) { get() >> { closure1() } },
      dependency2: Mock(Supplier) { get() >> { closure2() } },
      dependency3: Mock(Supplier) { get() >> { closure3() } }
    )

    expect:
    serviceUnderTest.method() == 'default value'

    where:
    serviceName | closure1                            | closure2                            | closure3
    "A"         | { throw new Exception('closure1') } | { null }                            | { null }
    "B"         | { null }                            | { throw new Exception('closure2') } | { null }
    "C"         | { null }                            | { null }                            | { throw new Exception('closure3') }
  }

  static class Service {
    def dependency1
    def dependency2
    def dependency3

    def method() {
      try {
        def foo = dependency1.get()
        def bar = dependency2.get()
        def baz = dependency3.get()
        return "$foo $bar $baz"
      } catch (Exception e) {
        println e
        return 'default value'
      }
    }
  }

  static class Supplier {
    def get() {
      "OK"
    }
  }

}

这是我的 IDE (IntelliJ IDEA) 展开时测试执行的样子: