0

I am using JavaFX to write a sample video player. But the tests fails when run together, ( Note: individually test passes).

Error: Unexpected exception thrown: java.lang.IllegalStateException: Application launch must not be called more than once

I understand that calling launch() twice on same Application Instance is causing this issue as per this . But I am not able to understand, that after one test completes, why the app is still running ? Why new instance is not getting created for 2nd test. testUnsupportedVideoFileThrowsError() succeeds but testSupportedVideoFilePlaysSuccessfully() fails.

package media;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;

import java.io.File;

public class PlayVideoSnippet extends Application {

    private static String source;
    private Media media = null;
    private MediaPlayer mediaPlayer = null;
    private MediaView mediaView = null;
    private Scene scene = null;

    public static void playVideo(String source) {
        PlayVideoSnippet.source = source;
        PlayVideoSnippet.launch();
    }

    @Override
    public void start(Stage stage) throws InterruptedException {
        try {
            media = new Media(new File(source).toURI().toString());
            //onError close the app
            media.setOnError(() -> {
                Platform.exit();
            });
            //Create MediaPlayer, with media
            mediaPlayer = new MediaPlayer(media);
            mediaPlayer.setAutoPlay(true);
            //onEnd of media, close the app
            mediaPlayer.setOnEndOfMedia(() -> {
                Platform.exit();
            });
            //Create media viewer
            mediaView = new MediaView(mediaPlayer);
            //create scene
            scene = new Scene(new Group(), media.getWidth(), media.getHeight());
            stage.setScene(scene);
            stage.setTitle("Video Player");
            //attach the mediaView to the scene root
            ((Group) scene.getRoot()).getChildren().add(mediaView);
            stage.show();
        } finally {
            System.out.println("Inside finally");
            stage.close();
        }

    }
}

package media;

import javafx.scene.media.MediaException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class PlayVideoSnippetTest {
    /**
     * Tests for {@link PlayVideoSnippet#playVideo(String)}.
     */

    final String PATH_OF_SUPPORTED_VIDEO_FILE = "src/test/resources/file_example_MP4_480_1_5MG.mp4";
    final String PATH_OF_UNSUPPORTED_VIDEO_FILE = "src/test/resources/file_example_WMV_480_1_2MB.wmv";


    @Test
    void testSupportedVideoFilePlaysSuccessfully() {

        assertDoesNotThrow(() -> PlayVideoSnippet.playVideo(PATH_OF_SUPPORTED_VIDEO_FILE));
    }

    @Test
    void testUnsupportedVideoFileThrowsError() {
        RuntimeException exception = assertThrows(RuntimeException.class, () -> PlayVideoSnippet.playVideo(PATH_OF_UNSUPPORTED_VIDEO_FILE));
        assertTrue(exception.getCause().getClass().equals(MediaException.class));
    }
}
Rohit Patel
  • 148
  • 1
  • 8
  • 4
    If you want to test JavaFX apps, look into a testing framework dedicated for that purpose: [TestFX](http://testfx.github.io/TestFX/). – jewelsea Oct 27 '22 at 10:52
  • The rule for `launch()` is not that it can only be called once per instance. The rule is that it can only be called once per *application*, i.e. it must only be called once in the lifecycle of the JVM. You would see the same error without the unit test, e.g. if you called `PlayVideoSnippet.playVideo(...)` twice from the `main(...)` method (or anywhere). If you want to be able to show multiple windows in JavaFX by calling a method multiple times, you should move that functionality out of `Application.start()`, make `start()` essentially a no-op, and call `launch()` once at startup. – James_D Oct 27 '22 at 13:31
  • @James_D So how to stop the JVM in between 2 test cases ? Tried calling ```Platform.exit()``` between each test case, not helpful !! Piece of code would be helpful. Thank you !! – Rohit Patel Oct 27 '22 at 14:43
  • You can't stop the JVM in between the test cases. The test cases are running in a JVM. As I described in my previous comment, your approach is incorrect: you **must not** call `launch()` more than once and you are setting up your code to do so. Just read the answer to the question you already linked. – James_D Oct 27 '22 at 14:54
  • Just to clarify: having a method (other than `main()`) which calls `launch()` is wrong. That's the part you need to fix. – James_D Oct 27 '22 at 15:00
  • Thanks @James_D Got your point. Will look into it !! – Rohit Patel Oct 27 '22 at 17:50

1 Answers1

0

I was able to fix the issue with help of below inputs:

  • Using TestFX for testing purpose as suggested by @jewelsea.
  • Inputs from @James_D
  • StackOver flow issue

ApplicationTest.launch() method internally takes care of setting up primary stage and cleaning before each test.

Solution:

import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;
class VideoTest extends ApplicationTest {
  
  final String pathOfSupportedFile = "video.mp4";
  final String pathOfUnsupportedFile = "video.wmv";


  @Test
  void testSupportedVideoFilePlaysSuccessfully() throws Exception {
    assertDoesNotThrow(() -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
            new String[]{pathOfSupportedFile}));
  }

  @Test
  void testUnsupportedVideoFileThrowsError() throws Exception {
    RuntimeException exception = assertThrows(RuntimeException.class,
        () -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
                    new String[]{pathOfUnsupportedFile}));
    assertTrue(exception.getCause().getCause().getClass().equals(MediaException.class));
  }
}
Rohit Patel
  • 148
  • 1
  • 8