0
votes

I'm using Groovy and Gradle for my testing.

I have the following lines in my app code:

Platform.runLater( new Runnable() {
    void run() {
    ....

        FileChooser fc = getFileChooser()
        File file = fc.showOpenDialog( null )

        // if( file != null && file.exists() ) { <--- this is what I have to put
        if( file.exists() ) { 
            println( "file $file exists")
            analyseFile( file )
        }

If I mock (using GroovyMock because javafx.stage.FileChooser is final) so that fc.showOpenDialog returns null I would expect a NullPointerException to be thrown on file.exists()... and one is.

But this doesn't show up in the test results, which are all green. The only way you find that this has happened is when you go and look at the test results for the class: then you see that the grey button saying "StdErr" is present.

This appears to be because the enveloping Runnable is "swallowing" it...

Is there any way in Spock to make an Exception inside a Runnable lead to test failure?

PS test code:

def "getting a null result from the showOpenDialog should be handled OK"(){
    given:
    FileChooser fc = GroovyMock( FileChooser )
    ConsoleHandler ch = Spy( ConsoleHandler ){ getFileChooser() >> fc }
    ch.setMaxLoopCount 10
    systemInMock.provideLines( "o" )
    fc.showOpenDialog( _ ) >> null

    when:
    com.sun.javafx.application.PlatformImpl.startup( {} )
    ch.loop()
    Thread.sleep( 1000L ) // NB I know this is a rubbish way to handle things... but I'm a newb with Spock!

    then:
    0 * ch.analyseFile( _ )
}

here's the SSCCE for the app code

class ConsoleHandler {
    int loopCount = 0
    def maxLoopCount = Integer.MAX_VALUE - 1
    def response
    FileChooser fileChooser = new FileChooser()

    void analyseFile( File file ) {
        // ...
    }
    void loop() {
        while( ! endConditionMet() ){
            print '> '
            response = System.in.newReader().readLine()
            if( response ) response = response.toLowerCase()
            if( response == 'h' || response == 'help' ){
                println "Help: enter h for this help, q to quit"
            }
            else if ( response == 'o' ){
                Platform.runLater( new Runnable() {
                    void run() {
                        FileChooser fc = getFileChooser()
                        File file = fc.showOpenDialog( null )
                        // if( file != null && file.exists() ) {
                        if( file.exists() ) {
                            analyseFile( file )
                        }
                    }
                })
            }
        }
    }
    boolean endConditionMet() {
        loopCount++
        response == 'q' || loopCount > maxLoopCount
    }
    static void main( args ) {
        com.sun.javafx.application.PlatformImpl.startup( {} )
        new ConsoleHandler().loop()
        com.sun.javafx.application.PlatformImpl.exit()
    }
}

The only other thing that might require an explanation is systemInMock in the Spock test code, which lets the test supply text to StdIn in the app code:

import org.junit.contrib.java.lang.system.TextFromStandardInputStream
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.emptyStandardInputStream
....
// field of the Test class, of course:
@Rule
public TextFromStandardInputStream systemInMock = emptyStandardInputStream()

The corresponding line in my Gradle dependencies clause is :

testCompile 'com.github.stefanbirkner:system-rules:1.16.0'

... but I think this could be obtained by using a @Grab in the Groovy test file, right?

I think I now realise this question is really about how best to do JavaFX-app-thread testing in Spock... EDT testing with Swing was always a problem, no doubt similar things apply. And it's all coming back to me: when you get Platform to run the Runnable it would make no sense to throw an Exception from runLater as the caller code has moved on, so I believe the only realistic option is for run() to call some other method which the testing code can call...

I have searched for "Spock JavaFX testing" but not much has come up...

1
What does your test code look like? - Szymon Stepniak
@SzymonStepniak ... Thanks ... I've just realised that the problem is not Spock-related but almost certainly a more classic one of an Exception being thrown inside a Runnable... but I'll add the test code... - mike rodent
Any chance you could come up with a minimal example that we can run? - tim_yates
@tim_yates thanks... It's several months since I was last doing Java / JUnit / Mockito testing ... but I do seem to remember that this is a problem with Runnables. I'm assuming presently that I shall have to have the Runnable call some other method which I can then run from the test... unless Groovy + Spock has some special magic to handle this. I'll try to put together a simple SSCCE for the app code a bit later. - mike rodent

1 Answers

0
votes

This is what I imagine to be the only practical way forward: change this bit like this:

else if ( response == 'o' ){
    Platform.runLater(new Runnable() {
        void run() {
            openFileChooser()
        }
    })
}

new method for ConsoleHandler:

def openFileChooser() {
    FileChooser fc = getFileChooser()
    File file = fc.showOpenDialog( null )
    // if( file != null && file.exists() ) {
    if( file.exists() ) {
        analyseFile( file )
    }
}

new Spock test:

def "getting a null result from the showOpenDialog should be handled OK2"(){
    given:
    FileChooser fc = GroovyMock( FileChooser )
    ConsoleHandler ch = Spy( ConsoleHandler ){ getFileChooser() >> fc }
    fc.showOpenDialog( _ ) >> null

    when:
    com.sun.javafx.application.PlatformImpl.startup( {} )
    ch.openFileChooser()
    Thread.sleep( 1000L )

    then:
    0 * ch.analyseFile( _ )
}

Test fails (hurrah!):

java.lang.NullPointerException: Cannot invoke method exists() on null object

But I'd welcome input about whether this can be improved, from more experienced Groovy/Spock hands.

Indeed I was slight puzzled why I didn't get "incorrect State" when I called openFileChooser in the when: clause. I presume this is because going showOpenDialog on a mock FileChooser doesn't actually involve any check to make sure we're currently in the JavaFX-app-thread. Turns out the ...PlatformImpl.startup( {} ) and Thread.sleep lines can be deleted in that test. Is this ideal practice though?! Hmmm...

later
Closing this as the only answer... in reality the same sorts of considerations apply to this question as to testing code in plain-old Java using plain-old JUnit in the JavaFX-app-thread. Spock + Groovy may pose some problems or they may make it easier... I am not yet experienced enough with them to know.