mike rodent
mike rodent

Reputation: 15662

Spock: capture return value from a method?

I have a functional test and I use a Spy for the CUT: I want to find out whether a method called in the midst of things returns expected result.

The sort of thing I thought of was something like this:

then:
1 * ch.compileOutputLines(_).size() == 5

But this doesn't work: Spock then complains

No signature of method: core.ConsoleHandler.compileOutputLines() is applicable for argument types: (org.spockframework.lang.Wildcard) values: [[*_]]

Same problem if I try:

then:
def result = 1 * ch.compileOutputLines(_)

But this doesn't really surprise me. If such a mechanism is available I presume you somehow have to configure the Spy in the given block.

Upvotes: 4

Views: 4413

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42224

When working with spied object you can invoke callRealMethod() that delegates method invocation to the real object. This way you can capture the returned value and apply some assertions to it. Consider following example:

import spock.lang.Specification

class SpyMethodArgsExampleSpec extends Specification {

    def "should check a value returned in spied object"() {
        given:
        ConsoleHandler ch = Spy(ConsoleHandler)

        when:
        ch.run()

        then:
        1 * ch.compileOutputLines(_, _) >> { args ->
            assert callRealMethod().size() == 1
        }
    }

    static class ConsoleHandler {
        void run() {
            compileOutputLines(4,5)
        }
        List<String> compileOutputLines(Integer n, Integer m) {
            return [] << n
        }
    }
}

In this scenario we are invoking ConsoleHandler.run() and we want to check if method compileOutputLines(Integer n, Integer m) (I just used those parameters as an example, I guess your method has different arguments list) returns a list of size 1 for any two parameters.

Of course this is how you can do it technically. However, this kind of testing may turn quickly into bottle-neck due to over-specifying test scenarios. The exemplary ConsoleHandler.run() method may be not the best choice to explain that, but basically if your method expects some arguments and returns some result, you should focus on deterministic execution flow (for those specific parameters I always get this result) rather than what kind of methods ConsoleHandler.run() invokes and what those methods return inside the method we are testing. Imagine you have to refactor the method you test and you decided to simplify its body and maybe get rid of ch.compileOutputLines() invocation. Your method still returns same results for the same set of parameters, but your test starts failing, because it focuses on internal parts way too much. Instead it should alarm you only if your method starts behaving incorrectly (e.g. it started returning incorrect results for the set of parameters that remain the same). I hope it helps.

Upvotes: 4

Related Questions