3

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.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
mike rodent
  • 14,126
  • 11
  • 103
  • 157

1 Answers1

4

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.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • 1
    This is a great answer, not mainly (but also) because of the nice trick with the spy and `callRealMethod()` which I also used before, but try to stay away from. First and foremost, I like and support the statements in the last paragraph. – kriegaex Feb 27 '18 at 01:18
  • 1
    Thanks for the answer to my question and the final para. The truth is that I kind of know something is generally "wrong" with my TDD approach, not only with this question, but generally: I find myself using Spy a lot, but the Spock (and other) documentation says "spies are not so good: try to rethink things". I've been trying to use a TDD approach for about a year now, and would never stop now... but what I really need is to work hand-in-hand with an expert telling me what I'm doing right and what I'm not. Not TDD 101, I've done that a few times: but how it works "in the real world". – mike rodent Feb 27 '18 at 06:53