Search code examples
javajavafxgroovywebviewspock

Mocked instance of final class in Spock behaves differently in test and dev code


In my JavaFX application I am using Spock and Groovy for testing. I have dedicated WebBrowserController for taking care of my JavafX WebView component. I wanted to test some functionalities that depend on current Location and Document of the WebView.

Relevant part of WebBrowserController:

public WebEngine getEngine() {
    return panel.getWebView().getEngine();
}

This is how I create an instance of WebBrowserController for my tests. Notice the GroovyMock I used there - ordinary Mock(...) does not work for final classes and WebEngine is a final class.

WebBrowserController getMockedControllerWithDocument(Document document) {
    WebBrowserController controller = Mock(WebBrowserController)
    controller.getEngine() >> GroovyMock(WebEngine) {
        getDocument() >> document
        getLocation() >> "some random string"
    }

    controller
}

The line below is under the test and it breaks. I would expect "some random string" to be returned but I just get failed test and NPE.

String url = controller.get().getEngine().getLocation()

Now the interesting part - I have examined the instance of WebEngine in two places - at the end of getMockedControllerWithDocument and at the line pasted above. What I found out is that it referenced the same object. Yet, when I invoked any of the stubbed methods outside of test code I was hit by NPE - getLocation() executed the actual implementation and not the stub (the original method is not just a simple getter and it uses a wrapped value in between).

Summing it up: why the hell does the exact same object behaves differently depending on the place its methods are being invoked?


Solution

  • Because GroovyMock, GroovySpy and GroovyStub only work as you expect for Groovy classes. When called by Java classes, they behave like normal Spock mocks. This is documented here:

    TIP

    When Should Groovy Mocks be Favored over Regular Mocks? Groovy mocks should be used when the code under specification is written in Groovy and some of the unique Groovy mock features are needed. When called from Java code, Groovy mocks will behave like regular mocks. Note that it isn’t necessary to use a Groovy mock merely because the code under specification and/or mocked type is written in Groovy. Unless you have a concrete reason to use a Groovy mock, prefer a regular mock.