Skip to the content.

Testing Controllers

In the following section, you will learn how to test a basic controller using TestFX and Mockito.

ControllerTest

Testing controllers using TestFX requires the test to extend from ApplicationTest. It is however recommended to create a helper class like ControllerTest extending ApplicationTest. This class will contain some common code to reduce the amount of boilerplate required for each controller test.

public class ControllerTest extends ApplicationTest {
    
    @Spy
    protected App app = new App();
    @Spy 
    protected ResourceBundle resources = ...; // Define common instances here and mock/spy them

    protected Stage stage; // Useful for checking the title for example

    @Override
    public void start(Stage stage) throws Exception {
        super.start(stage);
        this.stage = stage;
        stage.setX(0); // This fixes potential issues in headless environments
        stage.setY(0);
        app.start(stage);
        stage.requestFocus(); // Make the test use the correct stage
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        app.stop();
        app = null;
        stage = null;
    }
}

The main annotations offered by Mockito are @Spy and @Mock. Mocking an instance completely removes all default behaviour and content of methods, fields and such, resulting in an empty shell which can later be redefined. This is useful if the real behaviour isn’t needed at all, but the instance itself has to exist. Spying an instance doesn’t touch the default behaviour but allows redefining parts of the logic and checking whether methods have been called using verify.

Spies and Mocks can later be injected into the controller instance which is being tested using @InjectMocks.

Writing a real test

Since most of the setup is already defined in the ControllerTest class we can just extend it for our own tests. In order to get Mockito working, the class has to be annotated with @ExtendWith(MockitoExtension.class).

@ExtendWith(MockitoExtension.class)
public class SetupControllerTest extends ControllerTest {

    @InjectMocks
    SetupController setupController;

    @Override
    public void start(Stage stage) throws Exception {
        super.start(stage); // It is important to call super.start(stage) to setup the test correctly
        app.show(setupController);
    }

    @Test
    public void test() {
        // Since we don't really want to show a different controller, we mock the show() method's behaviour to just return null
        doReturn(null).when(app).show(any(), any());

        assertEquals("Ludo - Set up the game", app.stage().getTitle());

        // TestFX offers different methods for interacting with the application
        moveTo("2");
        moveBy(0, -20);
        press(MouseButton.PRIMARY);
        release(MouseButton.PRIMARY);
        clickOn("#startButton");

        waitForFxEvents(); // Wait for the logic to run
        
        // Mockito can be used to check if the show() method was called with certain arguments
        verify(app, times(1)).show("ingame", Map.of("playerAmount", 2));

    }

}

Whenever something is loading asynchronously the method waitForFxEvents() should be called before checking the results. This ensures that all JavaFX events have been run before continuing the tests. Another way of waiting is the sleep() method, which allows to wait for a predefined time. This is not recommended though as the defined time is either too long or too short and therefore can cause issues or unnecessary delays.


⬅ Setup Overview Testing SubComponents ➡