I am testing a service method which has a few dependencies; and I want to assert that if any of these dependencies throws an exception, the service method should return a default value.
The service and the test I'd like to write look something like this.
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') }
}
This test doesn't work, because it causes the mocks to return literal closures rather than the results of those closures. Of course this is caused by the addition of the where
block, as any mock can directly return the result of a single closure, i.e. dependency1.get() >> { throw new Exception() }
Am I forced to write this as three separate tests, or is there another way I can combine them?
If you write
dependency1.get() >> closure1
your mock will return the closure itself and not evaluate it. The evaluation only happens inside the GroovyString " $foo $bar $baz "
, expanding the error message happening during evaluation into it but not escalating that exception.
You want to use
dependency1.get() >> { closure1() }
in order to fix your test. The ()
evaluates your closure, but at the same time the surrounding closure {}
makes sure that the evaluation happens only when the stub method is called, not already when it is defined.
A few improvement ideas:
where:
block without semicolons and { -> ...
syntax.given:
block instead of in the then:
block where they don't belong?when: ... then:
in this simple case by expect:
as the Spock manual suggests?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"
}
}
}
This is what the test execution looks like in my IDE (IntelliJ IDEA) when unrolled: