我正在测试一个有一些依赖项的服务方法;我想断言,如果这些依赖项中的任何一个抛出异常,则服务方法应该返回默认值。
我想编写的服务和测试看起来像这样。
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
块造成的,因为任何模拟都可以直接返回单个闭包的结果,即 dependency1.get() >> { throw new Exception() }
我是否被迫将其编写为三个单独的测试,或者是否有其他方法可以将它们结合起来?
如果你写
dependency1.get() >> closure1
您的模拟将返回闭包本身而不对其进行评估。评估仅发生在 GroovyString
" $foo $bar $baz "
内部,将评估期间发生的错误消息扩展到其中,但不会升级该异常。
你想用
dependency1.get() >> { closure1() }
为了修复您的测试。
()
评估您的闭包,但同时周围的闭包 {}
确保仅在调用存根方法时才进行评估,而不是在定义它时进行评估。
一些改进想法:
where:
块,而无需分号和 { -> ...
语法。given:
块中而不是在它们不属于的 then:
块中存根模拟方法?when: ... then:
替换为 expect:
?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) 中的样子:
参考 @kriegaex 的答案,您可以通过将实际的异常抛出移至辅助方法来使其变得更加紧凑。此外,如果您需要检查异常冒泡/包装,外部字段也可以提供帮助。
这是一个更新的示例:
package de.scrum_master.stackoverflow.q57172322
import spock.lang.Specification
import spock.lang.Unroll
class ServiceDependenciesThrowingErrorsTest extends Specification {
def e(String name) {
throw new RuntimeException(name)
}
@Unroll
def 'handle error in service #serviceName'() {
given:
def serviceUnderTest = new Service(
dependency1: Mock(Supplier) { get() >> { a() } },
dependency2: Mock(Supplier) { get() >> { b() } },
dependency3: Mock(Supplier) { get() >> { c() } }
)
when:
serviceUnderTest.method()
then:
Exception ex = thrown()
ex.cause.message == name
where:
name | a | b | c
"A" | { e(name) } | {} | {}
"B" | {} | { e(name) } | {}
"C" | {} | {} | { e(name) }
}
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
throw e
}
}
}
static class Supplier {
def get() {
"OK"
}
}
}
这使得这种方法可以扩展到更多的依赖项,同时保持表格式。
但是,如果您想要以这种方式测试“许多”依赖项,那么数据管道可能会更好地为您服务,因为它们可以让您使用更少的代码构建有效的稀疏矩阵。以下是一些有用的示例片段:
// Build the closure matrix using list concatenations
closure1 << [{}] * 00 + [{ e() }] + [{}] * 04
closure2 << [{}] * 01 + [{ e() }] + [{}] * 03
// Or automate that a bit
closure1 << sparseExceptions(5, 0)
closure2 << sparseExceptions(5, 1)
def sparseExceptions(int count, int position) {
[{}] * position + [{ e() }] + [{}] * (count - 1 - position)
}
最后,稀疏矩阵的想法可以通过多变量数据管道发挥到极致。这允许在不牺牲名称的情况下进行显着扩展,但代价是失去对非异常存根值的控制:
[
name,
closure1,
closure2,
closure3
] << sparseMultiException([
"A",
"B",
"C"
])
def sparseExceptions(List<String> names) {
names.indexed().collect { y, name ->
[name] + (0..<names.size()).collect { x ->
sparseExceptions(names.size(), x)[y]
}
}
}