Test Groovy class that uses System.console()

Viewed 1263

I have a groovy script that asks some questions from the user via a java.io.console object using the readline method. In addition, I use it to ask for a password (for setting up HTTPS). How might I use Spock to Unit Test this code? Currently it complains that the object is a Java final object and can not be tested. Obviously, I'm not the first one trying this, so thought I would ask.

A sketch of the code would look something like:

class MyClass {

   def cons

   MyClass() {
     cons = System.console()
   }

   def getInput = { prompt, defValue ->
     def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
     def inputTest = input?.toLowerCase()
     input
   }
}

I would like Unit Tests to test that some mock response can be returned and that the default value can be returned. Note: this is simplified so I can figure out how to do the Unit Tests, there is more code in the getInput method that needs to be tested too, but once I clear this hurdle that should be no problem.

EDITED PER ANSWER BY akhikhl Following the suggestion, I made a simple interface:

interface TestConsole {
    String readLine(String fmt, Object ... args)
    String readLine()
    char[] readPassword(String fmt, Object ... args)
    char[] readPassword()
}

Then I tried a test like this:

def "Verify get input method mocking works"() {

    def consoleMock = GroovyMock(TestConsole)
    1 * consoleMock.readLine(_) >> 'validResponse'

    inputMethods = new MyClass()
    inputMethods.cons = consoleMock

    when:
    def testResult = inputMethods.getInput('testPrompt', 'testDefaultValue')
    then:
    testResult == 'validResponse'
}

I opted to not alter the constructor as I don't like having to alter my actual code just to test it. Fortunately, Groovy let me define the console with just a 'def' so what I did worked fine.

The problem is that the above does not work!!! I can't resist - this is NOT LOGICAL! Spock gets 'Lost' in GroovyMockMetaClass somewhere. If I change one line in the code and one line in the test it works.

Code change:

From:
    def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
To: (add the null param)
    def input = (cons.readLine(prompt, null).trim()?:defValue)?.toString()

Test change:

From:
    1 * consoleMock.readLine(_) >> 'validResponse'
To: (again, add a null param)
    1 * consoleMock.readLine(_, null) >> 'validResponse'

Then the test finally works. Is this a bug in Spock or am I just out in left field? I don't mind needing to do whatever might be required in the test harness, but having to modify the code to make this work is really, really bad.

1 Answers
Related