Search code examples
javaspring-bootunit-testingjavafxtestfx

Testing overriden EventHandler in Java (JavaFx, Spring Boot)


I have been writing a small application in Java using Spring Boot with a JavaFX front end.

I'm having trouble working out how to test part of one of the controllers. Specifically, the controller includes some event handling for when the 'confirm' or 'cancel' button is pressed. Pressing confirm should cause the selected item to be deleted from the database and the tableview. Pressing either button should cause the current window to close.

package application.controllers;

import ...

@Controller
public class ConfirmDeleteController {

    @FXML private Label labelConfirmDelete;
    @FXML private Button confirmButton;
    @FXML private Button cancelButton;

    private FooService fooService;
    private TableView<Foo> fooTable;
    private Foo foo = new foo();


    void initData(TableView<Foo> fooTable, FooService fooService) {

        this.fooTable = fooTable;
        this.fooService = fooService;

        foo = (Foo) fooTable.getSelectionModel().getSelectedItem();     

        [other irrelevant bits with test coverage already]

        initializeButtonEventHandling();
    }


    private void initializeButtonEventHandling() {

        confirmButton.defaultButtonProperty().bind(confirmButton.focusedProperty());
        cancelButton.defaultButtonProperty().bind(cancelButton.focusedProperty());

        confirmButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                fooService.delete(foo); // Delete from db
                fooTable.getItems().remove(foo);    // Delete from the table view                           
                confirmButton.getScene().getWindow().hide(); // Close this pop up
            }
        });

        cancelButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                cancelButton.getScene().getWindow().hide();
            }
        });
    }
}

For the test, I have:

public class TestConfirmDeleteController extends ApplicationTest {  

    @Mock private Foo foo;
    @Mock private FooService fooService;
    @Mock private TableViewSelectionModel<Foo> selectionModel;
    @Mock private TableView<Foo> fooTable;

    private ConfirmDeleteController confirmDeleteController;

    @Override
    public void start (Stage stage) throws Exception {

        // Mock core behaviour
        initMocks(this);    
        when(fooTable.getSelectionModel()).thenReturn(selectionModel);
        when(selectionModel.getSelectedItem()).thenReturn(foo);


        // Load and display
        FXMLLoader fxmlLoader = new FXMLLoader(
                getClass().getResource("/fxml/popups/ConfirmDelete.fxml"));

        stage.setScene(new Scene((Pane) fxmlLoader.load()));

        confirmDeleteController = 
                fxmlLoader.<ConfirmDeleteController>getController();

        stage.show();
    }


...

    @SuppressWarnings("unchecked")
    @Test
    public void testConfirmDeletion() {

        // Given
        ObservableList<Foo> listOfFoo = mock(ObservableList.class);
        Scene scene = mock(Scene.class);
        Window window = mock(Window.class);

        when(fooTable.getItems()).thenReturn(listOfFoo);
        when(listOfFoo.remove(foo)).thenReturn(true);
        when(scene.getWindow()).thenReturn(window);

        // When
        confirmDeleteController.initData(fooTable, fooService);
        clickOn("#confirmButton");


        // Then
        verify(fooService).delete(foo); // Delete from database
        verify(listOfFoo).remove(foo); // Delete from TableView
        verify(window).hide(); // Close the confirm delete window
    }

I have tried several ways of firing the event but have had no luck. I've tried replacing the button with a mock, injecting it, and then firing the event directly. I've tried using a TestFX robot to press the button (as above). In this case, you can SEE the robot click the button, but the assertions fail indicating that the selected item is not deleted etc.

Every approach feels a bit 'badly-executed integration test-esque', and so I feel like I may even need to refactor this to move the event handling elsewhere?

Could somebody please advise on how to tackle this and get proper test coverage? I'm banging my head against the wall at this stage.

I KNOW the code works, as I've manually tested it extensively, but can't work out how to unit test it.

Thank you.

Edit: I have now worked out that the issue was actually related to the Security & Privacy settings on my Mac. See below.


Solution

  • In case anybody is interested in future, the issue here actually turned out to be related to the Security & Privacy settings on Mac OS Catalina. Eclipse did not have permission to control the computer (Settings --> Security & Privacy --> Accessibility --> "Allow the apps below to control your computer".

    As a result, FxRobot was unable to actually click the button. You could see the mouse moving but nothing happened as a result.

    Updating the settings as above, re-starting Eclipse, and re-running the test allowed the test to actually click the buttons, and the events then fired as expected.