Search code examples
javafxgroovymockingspocktestfx

Why am I obliged to use a GroovyMock in this case?


Here is an MCVE:

main.groovy:

package core

import javafx.application.Application
import javafx.stage.Stage
import javafx.fxml.FXMLLoader

class App extends Application {
    FXMLLoader fxmlLoader = new FXMLLoader()
    static App instance
    FXMLController fxmlController
    void start(Stage primaryStage) {
        instance = this
        fxmlLoader.load( getClass().getResource("mainWindow.fxml").openStream() )
        primaryStage.show()
    }
}
class FXMLController {}

testfx.groovy:

package core

import javafx.fxml.FXMLLoader
import javafx.scene.Node
import javafx.stage.Stage
import org.testfx.framework.spock.ApplicationSpec

class FirstFXSpec extends ApplicationSpec {
    void start(Stage stage)  { }
    def 'at start, fxmlLoader should load mainWindow.fxml'() {
        given:
        App.instance = new App()
        App.instance.fxmlLoader = Mock(FXMLLoader) {
            getController() >> Mock(FXMLController)
        }
        Node mockNode = Mock(Node)
        // Stage mockStage = GroovyMock( Stage ){
        Stage mockStage = Mock( Stage ){

            show() >> null
        }

        when:
        App.instance.start( mockStage )

        then:
        true // details of test omitted
    }
}

build.gradle:

plugins {
    id 'groovy'
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories { mavenCentral() }
javafx {
    version = '11.0.2'
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}
dependencies {
    implementation 'org.codehaus.groovy:groovy:2.5.+'
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
    testImplementation 'net.bytebuddy:byte-buddy:1.9.3'
    testImplementation 'org.objenesis:objenesis:2.6'
    testImplementation 'org.testfx:testfx-spock:4.0.15-alpha'
}
application {
    mainClassName = 'core.App'
}

Details of the file src/main/resources/core/mainWindow.fxml are not important, but here's an example:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox prefHeight="600.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/11.0.1"
      xmlns:fx="http://javafx.com/fxml/1" fx:controller="core.FXMLController" >
    <children>
        <Label text="Some label"></Label>
    </children>
</VBox>

The above test fails. The real method Stage.show() is called and the fact that it is a mock is ignored.
If I change that from a Mock to a GroovyMock the mock method show() is used instead and the test passes.
Why does a normal Spock Mock get ignored?


Solution

  • Plain Java Mocks cannot mock final methods and according to the JavaDocs Stage.show is final. GroovyMocks work by using the groovy MOP and thus only work when called from groovy code, but they are more powerful in this regard.