首页 > 解决方案 > Spock 在 Where 块中测试异常处理

问题描述

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

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

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') }
}

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

我是否被迫将其编写为三个单独的测试,还是有另一种方法可以将它们结合起来?

标签: exceptiongroovyspockdata-driven-tests

解决方案


如果你写

dependency1.get() >> closure1

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

你想用

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

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

一些改进的想法:

  • 如何展开您的测试,将其拆分为多个具有参数化名称的方法?这还具有很好的副作用,可以帮助 IDE 和 Groovy 编译器在where:不使用分号和{ -> ...语法的情况下解析您的块。
  • given:在块中而不是then:在它们不属于的块中存根模拟方法怎么样?
  • 如何在模拟定义中存根方法以使测试更紧凑?
  • when: ... then:在这种简单的情况下,如何expect:按照 Spock 手册的建议替换?
package de.scrum_master.stackoverflow.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) 中的样子:

IntelliJ IDEA 中展开的测试执行


推荐阅读