2

I am completely stuck in a java test; it's about sending by the test method the character 'a' to the JTextField of a JFrame component.

The JFrame class implements the KeyListener interface, and as such overrides KeyPressed, KeyTyped, and KeyReleased. Along with this, I transfer all the keypressed of the JTextField to the JFrame; inside the JFrame constructor I have :

JTextField txf_version = new JTextField();
txf_version.addKeyListener(this);

I would like to test this behavior and then to simulate the action of type a character in the JTextField.

all my attempts failed; I tried with the java.awt.Robot class, like this : hava a look at this other post in stack overflow, but I get a strange behavior : calling

robot.keyPress(KeyEvent.VK_A);

displays the character in my IDE directly, not in the virtual JFrame! try to play with requestFocus() or requestFocusInWindow() is ineffective.

I also tried with KeyEvents:

KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
bookWindow.txf_version.dispatchEvent(key);

but again the textfield's text property is not changed...

here is the method I have for now:

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException {
  initTest();

  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);

  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
        ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());


}

I can have a look at the actual behavior by writing a main() method in the JFrame's child class, but I think it is useful to know how to simulate keys for swing components testing.

thank you

EDIT: I changed the code of my test according to AJNeufeld's answer, but it still doesn't work. Here is my test code :

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException,
      InvocationTargetException {
//bookEditor2 & bookWindow
SwingUtilities.invokeAndWait(() -> {
  bookWindow = new BookWindow();
  VectorPerso two = new VectorPerso();
  two.add(le_livre_de_la_jungle);
  two.add(elogeMaths);
  bookWindow.setTableDatas(two);
  bookWindow.table.setRowSelectionInterval(1, 1);
  bookWindow.txf_version.requestFocusInWindow();
  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System
          .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
          ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
});


}

the plintln line produces a text in the console : "dans txf_version : 0", which indicates the key isn't send to the txf_version.

EDIT 2:

new try:

@Test
  void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
          InterruptedException, NoSuchFieldException, IllegalAccessException,
          InvocationTargetException {
    //bookEditor2 & bookWindow
    SwingUtilities.invokeAndWait(() -> {
      bookWindow = new BookWindow();
      VectorPerso two = new VectorPerso();
      two.add(le_livre_de_la_jungle);
      two.add(elogeMaths);
      bookWindow.setTableDatas(two);
      bookWindow.table.setRowSelectionInterval(1, 1);
      bookWindow.txf_version.requestFocusInWindow();
      KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System

              .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
      bookWindow.txf_version.dispatchEvent(key);
    });
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    SwingUtilities.invokeAndWait(() -> {
      System.out.println("dans txf_version : " + bookWindow.txf_version.getText
              ());

      assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
    });


  }
lolveley
  • 1,659
  • 2
  • 18
  • 34

3 Answers3

1

I think you're doing a couple of things wrong, but without a complete example, it is hard to tell.

First, the JTextField is not really concerned with KEY_PRESSED events. It is concerned with the KEY_TYPED events.

Second, Swing processes events on the Event Dispatching Thread (EDT), which is not necessarily the thread that JUnit is going to be running on. You really shouldn't be changing things when you're not on the EDT. I'm not certain eventDispatch() does the switch to the EDT or not. It might. But it might also do it using invokeLater(), in which case the execution immediately passes to the assertEquals(), which fails, because the event processing hasn't happened yet.

Here is minimal, complete, verifiable example, which shows a keypress sent, which changes the button colour, and a JUnit test case which checks it and passes:

First, the code under test:

public class SwingUnitTesting extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(SwingUnitTesting::new);
    }

    JTextField tf = new JTextField();
    JButton btn = new JButton("Test Button");

    SwingUnitTesting() {
        add(tf, BorderLayout.PAGE_END);
        add(btn, BorderLayout.PAGE_START);

        tf.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                btn.setForeground(Color.RED);
            }
        });

        setSize(200, 80);
        setLocationByPlatform(true);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setVisible(true);
    }
}

And the unit test:

public class SwingUnitTestingTest {

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        SwingUtilities.invokeAndWait(() -> {
            sut = new SwingUnitTesting();
        });
    }

    @Test
    public void btnNotRedBeforeKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            assertNotEquals(Color.RED, sut.btn.getForeground());
        });
    }

    @Test
    public void btnRedAfterKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            sut.tf.requestFocusInWindow();
            sut.tf.dispatchEvent(new KeyEvent(sut.tf,
                    KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0,
                    KeyEvent.VK_UNDEFINED, 'A'));
            assertEquals(Color.RED, sut.btn.getForeground());
        });
    }
}

You can probably use some JUnit @Rule trickery or a custom runner to automatically change to the EDT when running swing tests.


Update:

I got curious, and tried to find an existing @Rule which puts the @Before, @Test, and @After code on to the EDT, but my Google-fu failed me; I know I've seen it before, but I couldn't find it. In the end, I created my own:

public class EDTRule implements TestRule {

    @Override
    public Statement apply(Statement stmt, Description dscr) {
        return new Statement() {
            private Throwable ex;

            @Override
            public void evaluate() throws Throwable {
                SwingUtilities.invokeAndWait(() -> {
                    try {
                        stmt.evaluate();
                    } catch (Throwable t) {
                        ex = t;
                    }
                });
                if (ex != null) {
                    throw ex;
                }
            }
        };
    }
}

Using this rule, the JUnit test becomes a little simpler:

public class SwingUnitTestingTest {

    @Rule
    public TestRule edt = new EDTRule();

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        sut = new SwingUnitTesting();
    }

    @Test
    public void btnNotRedBeforeKeypress() {
        assertNotEquals(Color.RED, sut.btn.getForeground());
    }

    @Test
    public void btnRedAfterKeypress() {
        sut.tf.requestFocusInWindow();
        sut.tf.dispatchEvent(
                new KeyEvent(sut.tf, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A'));
        assertEquals(Color.RED, sut.btn.getForeground());
    }
}

Tested on MacOS, with jdk1.8.0_121

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • hello, thank you for your answer. I don't know rules in JUnit, so I tried to adapt your answer without rules but there still is a problem. I put all the test code in the Swing thread, but the textField's text isn't changed. please have a look at my edit. – lolveley Nov 17 '17 at 14:03
  • You can’t (usefully) sleep in the EDT. Swing processes events in the EDT, so if you sleep that thread, nothing gets processed. Instead you need something like: `invokeAndWait(()->{...}); Thread.sleep(); invokeAndWait(()->{...});`. You can call `invokeAndWait` as many times as you need. – AJNeufeld Nov 17 '17 at 14:38
  • please have a look at my question, I updated it but it still doesn't work. It should work, I really don't understand why there is an error. – lolveley Nov 17 '17 at 14:45
  • the textfield contains its default value , 0, even after the injection of the keyEvent, at least the statements expected to do this. – lolveley Nov 17 '17 at 14:59
  • Have you tried my example & unit test? Did it work? If not, I need to know what platform are you on so I can debug in that environment. If it did work, we need to figure out what is different between my sample & yours. Strip out all unnecessary code - no tables, etc, just your frame, text field, key press handling, and button color code - test that, and if it doesn’t work post that [mcve]. If it does work, add in your table etc, one at a time, until it stops working. We can proceed from there. – AJNeufeld Nov 17 '17 at 15:29
  • 1
    done! I searched in my JFrame class, and it did work when I add some stuff concerning the window, in the constructor : setLocationByPlatform(true), setLocation(0,0),setSize(100,50),setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE), and almost setVisible(true). A headless test doesn't work apparently with this method! thanks again. – lolveley Nov 17 '17 at 18:28
  • Glad to hear you got it working; happy to help. Re: `setDefaultCloseOperation()`. Don’t use `EXIT_ON_CLOSE`; use `DISPOSE_ON_CLOSE`. You don’t want the JUnit test environment unexpectedly exiting when you close a frame in your unit test! – AJNeufeld Nov 17 '17 at 18:54
0

Yo, I guess you have two choices

1) It's still to find a solution using SWING (but in this case, I have no experience and any idead how to help you).

2) It's to use Sikulli java framework for testing desktop app. You can add the dependency then

  • make screenshot of your UI elements, and put them into test data folder of your app Using sikuli + JUnit
  • write simple test where you set a path for your pic of button (for example)
  • and write action, move, click or what actually you need. in very simple that will be looks like

    Button button = new Button("test-folder/pic1.jpg");

    button.click();

After run, you will see that, your cursor was move on button, and click by it.

For more details, find examples in web about Sikulli + Java.

Skytrace
  • 58
  • 4
  • thanks, it's a funny tool sikuli, but I it seems the site is down. Yet I would rather for this project to test my JFrame headlessly, to keep it simpler. – lolveley Nov 17 '17 at 14:14
0

recently I had to test a customized KeyAdapter and I created a customized JTextField to dispatch key events. A piece of the code I used is bellow:

public class ProcessKeyOnTextFieldTest {

    @Test
    public void pressKeyTest() throws Exception {
        JTextFieldWithTypedKeySupport textField = new JTextFieldWithTypedKeySupport();

        textField.pressKey('a');
        textField.pressKey('b');

        assertEquals("ab", textField.getText());
    }

    class JTextFieldWithTypedKeySupport extends JTextField {

        int timestamp;

        void pressKey(char key) throws InvocationTargetException, InterruptedException {
            SwingUtilities.invokeAndWait(() -> super.processKeyEvent(createEvent(key)));
        }

        KeyEvent createEvent(char keyChar) {
            return new KeyEvent(this, KeyEvent.KEY_TYPED, timestamp++, 0, KeyEvent.VK_UNDEFINED, keyChar);
        }
    }
}
Edu Costa
  • 1,414
  • 13
  • 13